Skip to content

Rename PDS members with STOW instead of asking LOCATE about them (#92) - #93

Merged
mgrossmann merged 1 commit into
mainfrom
fix/92-member-rename
Aug 7, 2026
Merged

Rename PDS members with STOW instead of asking LOCATE about them (#92)#93
mgrossmann merged 1 commit into
mainfrom
fix/92-member-rename

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Closes #92.

The defect

Measured on the target with a PDS holding M1, M2, M3:

> DELE 'IBMUSER.MBRTEST(M1)'          250 deleted.                  (worked)
> RNFR 'IBMUSER.MBRTEST(M2)'          350 RNFR accepted.
> RNTO 'IBMUSER.MBRTEST(M9)'          550 ... M9 already exists.    (M9 did not)
> RNFR 'IBMUSER.MBRTEST(NOSUCHMB)'    350 RNFR accepted.            (nor did that)

One cause behind all of it: __locate() resolves the base data set name and
ignores (member).
RNFR and RNTO kept the member attached and asked LOCATE,
so the "target must not exist" check saw the PDS and rejected every member of
it, the "source must exist" check passed for members that were not there, and
IDCAMS ALTER — which renames catalogued data sets, not directory entries —
could not have finished the job in any case.

The same blind spot is why SIZE could not handle members (#88); there it was
__dscbdv() that choked on the member form.

Change

Both handlers split the member off before doing anything else.

  • RNFR authorizes against the base name. A RAKF DATASET profile is held
    under a data set name, so PDS(MEMBER) never matched one — only the
    HLQ == userid short-circuit was hiding that. Existence is then checked in
    two parts: the data set with __locate(), the member with __listpd()
    through a new member_exists() helper.
  • RNTO dispatches on what the two names are:
    • member → member in the same PDS: __renmem() (clibio.h:167), whose STOW
      return codes are exactly the two answers LOCATE could not give — 8 source
      missing, 4 target occupied. Both are mapped to their own 550 text.
    • data set → data set: unchanged, IDCAMS ALTER.
    • member → data set, data set → member, member across two different PDSs:
      550 saying which of the three it was, rather than failing obscurely a few
      steps later.

member_exists() reads the directory under the session identity, like LIST
does, so the identity-window discipline from #79 holds — one window, opened and
closed inside the helper, no data set held across it.

What this does not change

DELE of a member keeps using IDCAMS; it works today, and __stow('D') would
need an open DCB the delete path does not otherwise have. Noted in #87, which
is where the "replace IDCAMS" idea now ends: its throughput premise was
measured away (0.11s median window), and for data sets there is no direct
route — libc370 uses CAMLST only for LOCATE and OBTAIN, both read-only, and the
catalog side would have to handle VSAM user catalogs.

Verification

Builds clean with -Wall -Werror. The behaviour needs the target: after
deploy, the transcript above must become 550 ... does not exist. for the
missing member on RNFR, and a successful 250 PDS(M2) renamed to PDS(M9) for
the rename — with the member actually moved in the directory, checked through
the REST API rather than through FTPD's own listing.

Renaming a member was impossible, and both ways of failing lied about the
reason.  On the target, with a PDS holding M1..M3:

    RNTO 'IBMUSER.MBRTEST(M9)'       550 ... M9 already exists.   (it did not)
    RNFR 'IBMUSER.MBRTEST(NOSUCHMB)' 350 RNFR accepted.           (it did not exist)

One cause: __locate() resolves the base data set name and ignores (member).
RNFR and RNTO kept the member attached and asked LOCATE, so the target check
saw the PDS and called the member "already existing", the source check passed
for members that were not there, and IDCAMS ALTER -- which renames catalogued
data sets, not directory entries -- could never have completed the operation
anyway.

Both now split the member off first:

- RNFR authorizes against the base name (a RAKF DATASET profile is held under
  a data set name, so PDS(MEMBER) never matched one; only the HLQ == userid
  short-circuit was covering that up), verifies the data set with __locate()
  and the member with __listpd() through the new member_exists() helper.
- RNTO renames member to member with __renmem(), whose STOW return codes are
  precisely the two answers LOCATE could not give: 8 for a missing source, 4
  for an occupied target.  Data set to data set keeps IDCAMS ALTER.
- The combinations STOW cannot express -- member to data set, data set to
  member, member across two different PDSs -- get a 550 naming which of them
  it was, instead of failing obscurely three steps later.

member_exists() reads the directory under the session identity, like LIST
does, so the window discipline from #79 holds.
@mgrossmann
mgrossmann merged commit dfabfdb into main Aug 7, 2026
1 check passed
@mgrossmann
mgrossmann deleted the fix/92-member-rename branch August 7, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Renaming a PDS member is impossible: RNFR/RNTO check the base data set and ignore the member

1 participant