sftp-rs: Quick Reference
Crate Info
Feature Flags
| Feature |
Default |
Requires |
Provides |
default |
✅ |
bin |
CLI binary |
bin |
✅ (via default) |
rustyline, shell-words |
sftp binary |
ssh2 |
❌ |
ssh2 crate |
TryFrom<ssh2::Channel> |
async |
❌ |
tokio |
AsyncSftpClient |
russh |
❌ |
russh, async, tokio |
russh transport glue |
Module Map
| Module |
Feature Gate |
Contents |
protocol |
always |
Wire codec: types, builders, parsers |
sync |
always |
SftpClient<C> |
r#async |
async |
AsyncSftpClient<W> |
russh |
russh |
from_channel(), from_subsystem_channel() |
Re-exports (lib.rs)
Client Construction
Operations Cheat Sheet
File Operations
| Operation |
Sync |
Async |
Request |
Response |
| Open |
open(path, opts, attrs) |
open(path, opts, attrs).await |
OPEN |
HANDLE → File |
| Read |
pread(&file, offset, len) |
pread(&file, offset, len).await |
READ |
DATA → Vec<u8> |
| Write |
pwrite(&file, offset, data) |
pwrite(&file, offset, data).await |
WRITE |
STATUS |
| Close |
fclose(&file) |
fclose(&file).await |
CLOSE |
STATUS |
| Line seek |
flineseek(&file, lineno) |
flineseek(&file, lineno).await |
EXTENDED "text-seek" |
STATUS/REPLY |
Directory Operations
| Operation |
Sync |
Async |
Request |
Response |
| Open dir |
opendir(path) |
opendir(path).await |
OPENDIR |
HANDLE → Directory |
| Read dir |
readdir(&dir) |
readdir(&dir).await |
READDIR |
NAME → Vec<(name, long, attrs)> |
| Close dir |
closedir(&dir) |
closedir(&dir).await |
CLOSE |
STATUS |
| Make dir |
mkdir(path, attrs) |
mkdir(path, attrs).await |
MKDIR |
STATUS |
| Remove dir |
rmdir(path) |
rmdir(path).await |
RMDIR |
STATUS |
Attribute Operations
| Operation |
Sync |
Async |
Request |
Response |
| Stat (follow) |
stat(path, flags) |
stat(path, flags).await |
STAT |
ATTRS |
| Lstat (no follow) |
lstat(path, flags) |
lstat(path, flags).await |
LSTAT |
ATTRS |
| Fstat (by handle) |
fstat(&file, flags) |
fstat(&file, flags).await |
FSTAT |
ATTRS |
| Set stat (path) |
setstat(path, attrs) |
setstat(path, attrs).await |
SETSTAT |
STATUS |
| Set stat (handle) |
fsetstat(&file, attrs) |
fsetstat(&file, attrs).await |
FSETSTAT |
STATUS |
Path Operations
| Operation |
Sync |
Async |
Request |
Response |
| Canonicalize |
realpath(path, ctrl, compose) |
realpath(path, ctrl, compose).await |
REALPATH |
NAME → String |
| Read symlink |
readlink(path) |
readlink(path).await |
READLINK |
NAME → String |
| Remove file |
remove(path) |
remove(path).await |
REMOVE |
STATUS |
| Rename |
rename(old, new, flags) |
rename(old, new, flags).await |
RENAME |
STATUS |
| Symlink |
symlink(path, target) |
symlink(path, target).await |
SYMLINK |
STATUS |
| Hard link |
hardlink(path, target) |
hardlink(path, target).await |
LINK |
STATUS |
| Link (generic) |
link(path, target, sym) |
link(path, target, sym).await |
LINK |
STATUS |
Lock Operations
| Operation |
Sync |
Async |
Request |
Response |
| Block |
block(&file, off, len, mask) |
block(&file, off, len, mask).await |
BLOCK |
STATUS |
| Unblock |
unblock(&file, off, len) |
unblock(&file, off, len).await |
UNBLOCK |
STATUS |
Extension Operations
| Operation |
Sync |
Async |
Request |
Response |
| Extended |
extended(req, data) |
extended(req, data).await |
EXTENDED |
REPLY → Option<Vec<u8>> |
OpenOptions Builder
Error Variants (Most Common)
| Error |
When |
Eof(msg, lang) |
End of file read, or end of directory listing |
NoSuchFile(msg, lang) |
File/path does not exist |
PermissionDenied(msg, lang) |
Insufficient permissions |
Failure(msg, lang) |
Generic failure |
DirNotEmpty(msg, lang) |
rmdir on non-empty directory |
NotADirectory(msg, lang) |
Expected directory, got file |
FileAlreadyExists(msg, lang) |
Exclusive create on existing file |
Io(err) |
Local I/O error or unexpected protocol message |
Other(code, msg, lang) |
Unrecognized SFTP status code |
Testing Approach
Both sync and async clients have extensive test suites using stub servers:
- Sync:
spawn_stub() on TCP sockets — a background thread that handles INIT/VERSION then routes requests through a handler closure
- Async:
with_stub() using tokio::io::duplex() — a spawned async task that runs a router against a handler closure
Both approaches allow per-test programmable server behavior: the handler receives (cmd, body_without_req_id) and returns (response_cmd, response_body_without_req_id).
The protocol module has unit tests for:
Attributes round-trip serialization (empty, all fields, individual field groups)
- Builder byte layout verification (exact byte-level assertions)
- Request-ID wrap/unwrap
- Status parsing (OK, EOF, typed errors)
- Expect functions (correct type, status-as-error, unexpected-type rejection)