Traversio

Mutations

Create directories, rename paths, remove files, and work with symlinks.

Supported Mutation Methods

The public SFTP wrapper supports:

try await sftp.setAttributes(
    "/tmp/traversio-demo.txt",
    attributes: SSHSFTPFileAttributes(
        flags: SSHSFTPFileAttributes.permissionsFlag,
        size: nil,
        userID: nil,
        groupID: nil,
        permissions: 0o640,
        accessTime: nil,
        modificationTime: nil,
        extensions: []
    )
)
try await sftp.makeDirectory("/tmp/traversio-demo")
try await sftp.rename("/tmp/original.txt", to: "/tmp/final.txt")
try await sftp.removeFile("/tmp/final.txt")
try await sftp.removeDirectory("/tmp/traversio-demo")

It also supports symlink operations:

try await sftp.createSymbolicLink(
    targetPath: "/tmp/final.txt",
    linkPath: "/tmp/final-link.txt"
)

let linkTarget = try await sftp.readLink("/tmp/final-link.txt")
print(linkTarget.filename)

Handle-scoped attribute mutation is also public:

let handle = try await sftp.openFile(
    "/tmp/traversio-demo.txt",
    flags: [.read, .write]
)

try await handle.setAttributes(
    SSHSFTPFileAttributes(
        flags: SSHSFTPFileAttributes.permissionsFlag,
        size: nil,
        userID: nil,
        groupID: nil,
        permissions: 0o600,
        accessTime: nil,
        modificationTime: nil,
        extensions: []
    )
)

try await handle.close()

Rename Behavior

When the server advertises [email protected] version 1 or later, Traversio automatically uses that extension. Otherwise it falls back to standard SSH_FXP_RENAME.

That stronger rename behavior is automatic from the caller's point of view:

try await sftp.rename("/tmp/current.txt", to: "/tmp/next.txt")

Worked Example

let root = try await sftp.realPath(".")

let directory = "\(root.filename)/traversio-doc-demo"
let file = "\(directory)/message.txt"
let renamed = "\(directory)/message-final.txt"
let symlink = "\(directory)/message-link.txt"

try await sftp.makeDirectory(directory)
try await sftp.writeFile(file, data: Array("hello\n".utf8))
try await sftp.setAttributes(
    file,
    attributes: SSHSFTPFileAttributes(
        flags: SSHSFTPFileAttributes.permissionsFlag,
        size: nil,
        userID: nil,
        groupID: nil,
        permissions: 0o640,
        accessTime: nil,
        modificationTime: nil,
        extensions: []
    )
)
try await sftp.rename(file, to: renamed)
try await sftp.createSymbolicLink(targetPath: renamed, linkPath: symlink)

let linkTarget = try await sftp.readLink(symlink)
print(linkTarget.filename)

try await sftp.removeFile(symlink)
try await sftp.removeFile(renamed)
try await sftp.removeDirectory(directory)

Limits

Mutation coverage is already broad enough for real application use:

  • one SFTPClient overlaps path and handle mutation requests through request-id-based reply routing
  • bulk helper reads and writes keep a simple whole-file model
  • broader extension coverage remains focused on the documented OpenSSH paths
  • wider cross-server validation remains release work

On this page