Skip to content

SFTP slashes for z/Os, improved error messages #9033 - #9043

Open
bamaer wants to merge 4 commits into
apache:mainfrom
bamaer:9033
Open

bamaer wants to merge 4 commits into
apache:mainfrom
bamaer:9033

Conversation

@bamaer

@bamaer bamaer commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

…ath in SFTP

fixes #9033

Please add a meaningful description for your change here


Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:

  • Run mvn clean install apache-rat:check to make sure basic checks pass. A more thorough check will be performed on your pull request automatically.
  • If you have a group of commits related to the same change, please squash your commits into one and force push your branch using git rebase -i.
  • Mention the appropriate issue in your description (for example: addresses #123), if applicable.

To make clear that you license your contribution under the Apache License Version 2.0, January 2004
you have to acknowledge this by using the following check-box.

@bamaer bamaer changed the title 9033 SFTP slashes for z/Os, improved error messages #9033 Oct 20, 2026
@hansva

hansva commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

1. Must fix: SftpClient still sends //X as is. chdir(), folderExists(), createFolder() (and getFileType(), delete(), renameFile() with absolute paths) don't add the trailing slash, so jsch still turns a single-level data set path into /X for ActionSftp and ActionSftpPut. Move the rule into one helper and use it in SftpFileObject.remotePath and in those methods:

public static String keepDataSetPrefix(String path) {
  return path != null && path.length() > 2 && path.startsWith("//")
          && path.charAt(2) != '/' && path.indexOf('/', 2) < 0
      ? path + "/"
      : path;
}

Add SftpClient cases to SftpDoubleSlashPathTest. If you'd rather leave this out of the PR, change "fixes #9033" to "addresses #9033" and open a follow-up issue.

2. Must fix: PR description. It's still the template text. Add the cause, the fix, and what stays open from #9033: the hint for invalid data set names, and the jsch upstream report.

3. Should do: check a sequential data set. Every single-level path now ends in /, files included. Read and write one single-level sequential data set on z/OS. If //HLQ.SEQ/ is refused, say in the comment on remotePath that only folders work.

4. Nit: add the z/OS sentence of SftpPut.Error.TargetIsNotAFile only for // paths (targetFile.getName() instanceof IRootPrefixFileName p && !p.getRootPrefix().isEmpty()).

&& path.startsWith("//")
&& path.charAt(2) != '/'
&& path.indexOf('/', 2) < 0
? path + "/"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] keepDataSetPrefix appends / to every single-level // path, and SftpFileObject.remotePath plus SftpClient stat/delete/rename/cd all use it. jsch then sends //HLQ.SEQ/ for a sequential data set opened as a file, not only for a prefix used as a folder. The javadoc says that file form has not been verified on z/OS; a trailing slash is the directory/prefix spelling, so a read, write, or rm can miss //HLQ.SEQ or address a different name. Previously glob turned it into /HLQ.SEQ, so this is not a regression of a working path, but it is a different path.

Suggestion: Read and write one single-level sequential data set on z/OS. If //HLQ.SEQ/ is accepted, say so here. If it is refused, say in this comment that only a folder/prefix is covered, and do not treat file get/put/rm of //HLQ.SEQ as fixed.

SftpPut.Error.CanNotFindRemoteFolder=Unable to find remote folder [{0}]
SftpPut.Error.MoveToDestinationFolderIsEmpty=The folder to move the source file to is empty
SftpPut.Error.UnableToUpload=Unable to upload to [{0}]
SftpPut.Error.TargetIsNotAFile=Unable to upload to [{0}]: it already exists on the server and isn''t a file, so it can''t be overwritten. On z/OS this is an existing data set the server lists as a folder, or a name other data sets are stored under.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] The z/OS sentence is appended for every non-file target, including an ordinary directory on a Unix SFTP server.

Suggestion: Keep the first sentence for every target. Add the z/OS clause only when the name is a // path (targetFile.getName() is an IRootPrefixFileName with a non-empty root prefix).

if (rootCause == null || rootCause == e) {
return e.toString();
}
return e.toString() + Const.CR + "Root cause: " + ExceptionUtils.getRootCauseMessage(e);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Root cause: is a new string shown on the error row, and it is hardcoded in English while the other SFTP Put errors go through the message bundle.

Suggestion: Move it to messages_en_US.properties (for example SftpPut.Error.RootCause=Root cause: {0}) and pass ExceptionUtils.getRootCauseMessage(e) as the argument.

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.

[Bug]: SFTP: a z/OS data set path with a single level (//HLQ.NAME) loses its second slash in jsch

3 participants