Skip to content

FIX: SFTP - preserve symlinks when copying to remote and syncing - #2919

Open
emilioheredia-source wants to merge 1 commit into
doublecmd:masterfrom
emilioheredia-source:fix/sftp-symlink-upload-v2
Open

emilioheredia-source wants to merge 1 commit into
doublecmd:masterfrom
emilioheredia-source:fix/sftp-symlink-upload-v2

Conversation

@emilioheredia-source

Copy link
Copy Markdown
Contributor

When copying a local directory tree to an SFTP destination, symlinks were silently converted to regular files. Three layered bugs:

  1. uwfxplugincopyinoperation.pas: the tree builder used fsooslFollow, resolving symlinks to their targets before StoreFile was called.

  2. uwfxpluginutil.pas / ProcessLink: no else-branch for the no-subnode case (fsooslDontFollow), leaving Result uninitialised.

  3. sftpsend.pas / StoreFile: opened local path with TFileStreamEx which follows symlinks; never tried to create a remote symlink.

Fix - copy:

  • sftpsend.pas: detect symlinks via fpLStat/fpReadLink before opening. Unlink any existing destination, then call libssh2_sftp_symlink to recreate the link on the remote (rsync-like behaviour). Falls back to content upload if the server refuses. Unix-only ({ UNIX}).
  • uwfxplugincopyinoperation.pas: switch SymLinkOption to fsooslDontFollow.
  • uwfxpluginutil.pas / ProcessLink: add else-branch for the no-subnode case, routing the symlink node to ProcessFile.

Fix - sync comparison:

  • sftpsend.pas / FsFindNextW: save the symlink's own attributes before following the link target to detect directories; restore mtime, atime and filesize afterwards (symlink filesize = byte length of target string).
  • fsyncdirsdlg.pas / AreEquivalentLinks: when one side does not expose the link target (e.g. WFX/SFTP plugin), fall back to size comparison.
  • fsyncdirsdlg.pas / UpdateState: let AreEquivalentLinks override the timestamp gate so symlinks compare as equal regardless of mtime.

End result:

After this fix, it is possible to copy a local directory tree to a remote Linux host over SFTP and use the folder sync tool to verify that the remote is an exact clone of the source — symlinks included. This covers a common workflow (backup, deployment, mirroring) that previously required falling back to rsync or similar command-line tools, and replaces it with a native GUI operation inside Double Commander.

When copying a local directory tree to an SFTP destination, symlinks
were silently converted to regular files. Three layered bugs:

1. uwfxplugincopyinoperation.pas: the tree builder used fsooslFollow,
   resolving symlinks to their targets before StoreFile was called.

2. uwfxpluginutil.pas / ProcessLink: no else-branch for the no-subnode
   case (fsooslDontFollow), leaving Result uninitialised.

3. sftpsend.pas / StoreFile: opened local path with TFileStreamEx which
   follows symlinks; never tried to create a remote symlink.

Fix - copy:
- sftpsend.pas: detect symlinks via fpLStat/fpReadLink before opening.
  Unlink any existing destination, then call libssh2_sftp_symlink to
  recreate the link on the remote (rsync-like behaviour). Falls back to
  content upload if the server refuses. Unix-only ({ UNIX}).
- uwfxplugincopyinoperation.pas: switch SymLinkOption to fsooslDontFollow.
- uwfxpluginutil.pas / ProcessLink: add else-branch for the no-subnode
  case, routing the symlink node to ProcessFile.

Fix - sync comparison:
- sftpsend.pas / FsFindNextW: save the symlink's own attributes before
  following the link target to detect directories; restore mtime, atime
  and filesize afterwards (symlink filesize = byte length of target string).
- fsyncdirsdlg.pas / AreEquivalentLinks: when one side does not expose
  the link target (e.g. WFX/SFTP plugin), fall back to size comparison.
- fsyncdirsdlg.pas / UpdateState: let AreEquivalentLinks override the
  timestamp gate so symlinks compare as equal regardless of mtime.

This branch has not been deployed

No deployments
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.

1 participant