Files
alknet/docs/research/references/ssh/russh-sftp/07-quick-reference.md

6.6 KiB

russh-sftp: Quick Reference

Crate Overview

Property Value
Version 2.3.0
License Apache-2.0
Protocol SFTP v3 (draft-ietf-secsh-filexfer-02)
Min Rust 2021 edition
WASM Client works on wasm32; server not supported

Feature Flags

Feature Default Description
async-trait Enables #[async_trait] on Handler traits

Key Dependencies

tokio (io-util, rt, sync, time, macros), tokio-util, serde, serde_bytes, bitflags, bytes, dashmap, chrono, thiserror, log

Public Modules

Module Description
client Client-side: RawSftpSession, SftpSession, Handler, Config, fs::File, fs::ReadDir, fs::DirEntry
server Server-side: Handler, StatusReply, Config, run(), run_with_config()
protocol All SFTP packet types, Packet enum, StatusCode, OpenFlags, FileAttributes, File, etc.
extensions OpenSSH extensions: LimitsExtension, HardlinkExtension, FsyncExtension, StatvfsExtension, Statvfs
de from_bytes() — public deserialization function for extension data
ser to_bytes() — public serialization function

Client Quick Start

use russh_sftp::client::SftpSession;
use russh_sftp::protocol::OpenFlags;

// Connect (using any AsyncRead+AsyncWrite stream)
let sftp = SftpSession::new(stream).await?;

// Or with config:
let sftp = SftpSession::new_with_config(stream, Config {
    max_packet_len: 262144,
    max_concurrent_writes: 8,
    request_timeout_secs: 30,
}).await?;

// File operations
let mut file = sftp.open("remote.txt").await?;           // read-only
let mut file = sftp.create("new.txt").await?;              // create+truncate+write
let mut file = sftp.open_with_flags("f", OpenFlags::READ | OpenFlags::WRITE).await?;

file.write_all(b"hello").await?;
file.flush().await?;            // drains write pipeline + optional fsync
file.rewind().await?;
let mut buf = Vec::new();
file.read_to_end(&mut buf).await?;
file.shutdown().await?;        // properly closes handle

// Directory operations
for entry in sftp.read_dir(".").await? {
    println!("{}: {:?}", entry.file_name(), entry.file_type());
}

// Other operations
sftp.canonicalize(".").await?;
sftp.metadata("file").await?;
sftp.symlink_metadata("link").await?;
sftp.create_dir("dir").await?;
sftp.remove_dir("dir").await?;
sftp.remove_file("file").await?;
sftp.rename("old", "new").await?;
sftp.symlink("target", "link").await?;
sftp.read_link("link").await?;
sftp.hardlink("src", "dst").await?;   // returns false if unsupported
sftp.fs_info("/").await?;             // returns Ok(None) if unsupported
sftp.close().await?;

Server Quick Start

use russh_sftp::protocol::{File, Handle, Name, Status, StatusCode, Version};
use russh_sftp::server::{Handler, StatusReply};

struct MyHandler;

impl Handler for MyHandler {
    type Error = StatusCode;

    fn unimplemented(&self) -> Self::Error {
        StatusCode::OpUnsupported
    }

    async fn init(&mut self, version: u32, _ext: HashMap<String, String>)
        -> Result<Version, Self::Error>
    {
        Ok(Version::new())
    }

    // ... implement methods as needed
}

// In your SSH server handler:
async fn subsystem_request(&mut self, channel_id: ChannelId, name: &str, session: &mut Session)
    -> Result<(), Error>
{
    if name == "sftp" {
        let channel = self.get_channel(channel_id).await;
        session.channel_success(channel_id)?;
        russh_sftp::server::run(channel.into_stream(), MyHandler).await;
    }
    Ok(())
}

RawSftpSession Quick Reference

use russh_sftp::client::RawSftpSession;

let session = RawSftpSession::new(stream);
// or: RawSftpSession::new_with_config(stream, config);

// Must call init first
let version = session.init().await?;

// Request-response methods
let handle = session.open("file", OpenFlags::READ, FileAttributes::empty()).await?;
let data = session.read(handle.handle, 0, 32768).await?;
session.write(handle.handle, 0, vec![1,2,3]).await?;
session.close(handle.handle).await?;
let attrs = session.stat("/path").await?;
let name = session.realpath(".").await?;
let dir_handle = session.opendir("/").await?;
let entries = session.readdir(dir_handle.handle).await?;
session.close(dir_handle.handle).await?;

// Extensions
let limits = session.limits().await?;
session.hardlink("/old", "/new").await?;
session.fsync(handle).await?;
let fs_info = session.statvfs("/").await?;

StatusCode Reference

Code Constant Meaning
0 Ok Successful completion
1 Eof End of file / no more directory entries
2 NoSuchFile File does not exist
3 PermissionDenied Insufficient permissions
4 Failure Generic failure
5 BadMessage Badly formatted packet
6 NoConnection Client-side: no connection (never from server)
7 ConnectionLost Client-side: connection lost (never from server)
8 OpUnsupported Operation not supported

OpenFlags Reference

Flag Value Description
READ 0x01 Open for reading
WRITE 0x02 Open for writing
APPEND 0x04 Append to existing data
CREATE 0x08 Create if doesn't exist
TRUNCATE 0x10 Truncate to zero length
EXCLUDE 0x20 Fail if file exists (must be with CREATE)

FileAttr (Attribute Flags) Reference

Flag Value Fields Present
SIZE 0x00000001 size: u64
UIDGID 0x00000002 uid: u32, gid: u32
PERMISSIONS 0x00000004 permissions: u32
ACMODTIME 0x00000008 atime: u32, mtime: u32
EXTENDED 0x80000000 (not yet implemented)

FileMode (File Type) Reference

Constant Value Type
FIFO 0x1000 Named pipe
CHR 0x2000 Character device
DIR 0x4000 Directory
NAM 0x5000 Named file
BLK 0x6000 Block device
REG 0x8000 Regular file
LNK 0xA000 Symbolic link
SOCK 0xC000 Socket

Packet Wire Format

All SFTP packets:

[u32 length] [u8 type] [payload...]
  • length = size of type byte + payload (does not include the length field itself)
  • Strings: [u32 len] [utf8 bytes]
  • Byte arrays: [u32 len] [raw bytes]
  • FileAttributes: [u32 flags] [conditional fields based on flags]

Extension Names

Extension Name Version
Limits limits@openssh.com 1
Hardlink hardlink@openssh.com 1
Fsync fsync@openssh.com 1
Statvfs statvfs@openssh.com 2