summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2014-09-19 17:34:02 -0700
committerAlex Crichton <alex@alexcrichton.com>2014-09-19 18:00:07 -0700
commitb4a344e1cc48b8cf8e6aa9957b090e73f6f31646 (patch)
treed0c3910a1483d5f0f49e29add45d596888c2a380
parentfb3186e7285977d88babc4ce13bf93a6bc76ccb5 (diff)
downloadssh2-rs-b4a344e1cc48b8cf8e6aa9957b090e73f6f31646.zip
Bind SFTP functionality.
-rw-r--r--libssh2-sys/lib.rs106
-rw-r--r--src/error.rs3
-rw-r--r--src/lib.rs4
-rw-r--r--src/sftp.rs481
-rw-r--r--tests/sftp.rs35
5 files changed, 627 insertions, 2 deletions
diff --git a/libssh2-sys/lib.rs b/libssh2-sys/lib.rs
index 8270c72..202574c 100644
--- a/libssh2-sys/lib.rs
+++ b/libssh2-sys/lib.rs
@@ -6,6 +6,7 @@ extern crate libc;
extern crate "link-config" as link_conifg;
use libc::{c_int, size_t, c_void, c_char, c_long, c_uchar, c_uint, c_ulong};
+use libc::ssize_t;
pub static SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT: c_int = 1;
pub static SSH_DISCONNECT_PROTOCOL_ERROR: c_int = 2;
@@ -112,6 +113,34 @@ pub static LIBSSH2_KNOWNHOST_KEY_SSHRSA: c_int = 2 << 18;
pub static LIBSSH2_KNOWNHOST_KEY_SSHDSS: c_int = 3 << 18;
pub static LIBSSH2_KNOWNHOST_KEY_UNKNOWN: c_int = 7 << 18;
+pub static LIBSSH2_FXF_READ: c_ulong = 0x00000001;
+pub static LIBSSH2_FXF_WRITE: c_ulong = 0x00000002;
+pub static LIBSSH2_FXF_APPEND: c_ulong = 0x00000004;
+pub static LIBSSH2_FXF_CREAT: c_ulong = 0x00000008;
+pub static LIBSSH2_FXF_TRUNC: c_ulong = 0x00000010;
+pub static LIBSSH2_FXF_EXCL: c_ulong = 0x00000020;
+
+pub static LIBSSH2_SFTP_OPENFILE: c_int = 0;
+pub static LIBSSH2_SFTP_OPENDIR: c_int = 1;
+
+pub static LIBSSH2_SFTP_ATTR_SIZE: c_ulong = 0x00000001;
+pub static LIBSSH2_SFTP_ATTR_UIDGID: c_ulong = 0x00000002;
+pub static LIBSSH2_SFTP_ATTR_PERMISSIONS: c_ulong = 0x00000004;
+pub static LIBSSH2_SFTP_ATTR_ACMODTIME: c_ulong = 0x00000008;
+pub static LIBSSH2_SFTP_ATTR_EXTENDED: c_ulong = 0x80000000;
+
+pub static LIBSSH2_SFTP_STAT: c_int = 0;
+pub static LIBSSH2_SFTP_LSTAT: c_int = 1;
+pub static LIBSSH2_SFTP_SETSTAT: c_int = 2;
+
+pub static LIBSSH2_SFTP_SYMLINK: c_int = 0;
+pub static LIBSSH2_SFTP_READLINK: c_int = 1;
+pub static LIBSSH2_SFTP_REALPATH: c_int = 2;
+
+pub static LIBSSH2_SFTP_RENAME_OVERWRITE: c_long = 0x1;
+pub static LIBSSH2_SFTP_RENAME_ATOMIC: c_long = 0x2;
+pub static LIBSSH2_SFTP_RENAME_NATIVE: c_long = 0x4;
+
pub enum LIBSSH2_SESSION {}
pub enum LIBSSH2_AGENT {}
pub enum LIBSSH2_CHANNEL {}
@@ -138,6 +167,32 @@ pub struct libssh2_knownhost {
pub typemask: c_int,
}
+#[repr(C)]
+pub struct LIBSSH2_SFTP_ATTRIBUTES {
+ pub flags: c_ulong,
+ pub filesize: u64,
+ pub uid: c_ulong,
+ pub gid: c_ulong,
+ pub permissions: c_ulong,
+ pub atime: c_ulong,
+ pub mtime: c_ulong,
+}
+
+#[repr(C)]
+pub struct LIBSSH2_SFTP_STATVFS {
+ pub f_bsize: u64,
+ pub f_frsize: u64,
+ pub f_blocks: u64,
+ pub f_bfree: u64,
+ pub f_bavail: u64,
+ pub f_files: u64,
+ pub f_ffree: u64,
+ pub f_favail: u64,
+ pub f_fsid: u64,
+ pub f_flag: u64,
+ pub f_namemax: u64,
+}
+
pub type LIBSSH2_ALLOC_FUNC = extern fn(size_t, *mut *mut c_void) -> *mut c_void;
pub type LIBSSH2_FREE_FUNC = extern fn(*mut c_void, *mut *mut c_void);
pub type LIBSSH2_REALLOC_FUNC = extern fn(*mut c_void, size_t, *mut *mut c_void)
@@ -410,4 +465,55 @@ extern {
pub fn libssh2_sftp_init(sess: *mut LIBSSH2_SESSION) -> *mut LIBSSH2_SFTP;
pub fn libssh2_sftp_shutdown(sftp: *mut LIBSSH2_SFTP) -> c_int;
pub fn libssh2_sftp_last_error(sftp: *mut LIBSSH2_SFTP) -> c_ulong;
+ pub fn libssh2_sftp_open_ex(sftp: *mut LIBSSH2_SFTP,
+ filename: *const c_char,
+ filename_len: c_uint,
+ flags: c_ulong,
+ mode: c_long,
+ open_type: c_int) -> *mut LIBSSH2_SFTP_HANDLE;
+ pub fn libssh2_sftp_close_handle(handle: *mut LIBSSH2_SFTP_HANDLE) -> c_int;
+ pub fn libssh2_sftp_mkdir_ex(sftp: *mut LIBSSH2_SFTP,
+ path: *const c_char,
+ path_len: c_uint,
+ mode: c_long) -> c_int;
+ pub fn libssh2_sftp_fsync(handle: *mut LIBSSH2_SFTP_HANDLE) -> c_int;
+ pub fn libssh2_sftp_fstat_ex(handle: *mut LIBSSH2_SFTP_HANDLE,
+ attrs: *mut LIBSSH2_SFTP_ATTRIBUTES,
+ setstat: c_int) -> c_int;
+ pub fn libssh2_sftp_fstatvfs(handle: *mut LIBSSH2_SFTP_HANDLE,
+ attrs: *mut LIBSSH2_SFTP_STATVFS) -> c_int;
+ pub fn libssh2_sftp_stat_ex(sftp: *mut LIBSSH2_SFTP,
+ path: *const c_char,
+ path_len: c_uint,
+ stat_type: c_int,
+ attrs: *mut LIBSSH2_SFTP_ATTRIBUTES) -> c_int;
+ pub fn libssh2_sftp_read(handle: *mut LIBSSH2_SFTP_HANDLE,
+ buf: *mut c_char,
+ len: size_t) -> ssize_t;
+ pub fn libssh2_sftp_symlink_ex(sftp: *mut LIBSSH2_SFTP,
+ path: *const c_char,
+ path_len: c_uint,
+ target: *mut c_char,
+ target_len: c_uint,
+ link_type: c_int) -> c_int;
+ pub fn libssh2_sftp_rename_ex(sftp: *mut LIBSSH2_SFTP,
+ src: *const c_char,
+ src_len: c_uint,
+ dst: *const c_char,
+ dst_len: c_uint,
+ flags: c_long) -> c_int;
+ pub fn libssh2_sftp_rmdir_ex(sftp: *mut LIBSSH2_SFTP,
+ path: *const c_char,
+ path_len: c_uint) -> c_int;
+ pub fn libssh2_sftp_write(handle: *mut LIBSSH2_SFTP_HANDLE,
+ buffer: *const c_char,
+ len: size_t) -> ssize_t;
+ pub fn libssh2_sftp_tell64(handle: *mut LIBSSH2_SFTP_HANDLE) -> u64;
+ pub fn libssh2_sftp_seek64(handle: *mut LIBSSH2_SFTP_HANDLE, off: u64);
+ pub fn libssh2_sftp_readdir_ex(handle: *mut LIBSSH2_SFTP_HANDLE,
+ buffer: *mut c_char,
+ buffer_len: size_t,
+ longentry: *mut c_char,
+ longentry_len: size_t,
+ attrs: *mut LIBSSH2_SFTP_ATTRIBUTES) -> c_int;
}
diff --git a/src/error.rs b/src/error.rs
index e7d9542..baf82a5 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -95,6 +95,9 @@ impl Error {
/// Get the message corresponding to this error
pub fn message(&self) -> &str { self.msg }
+
+ /// Return the code for this error
+ pub fn code(&self) -> libc::c_int { self.code }
}
impl fmt::Show for Error {
diff --git a/src/lib.rs b/src/lib.rs
index da6a70f..1995ae8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -64,7 +64,9 @@ pub use error::Error;
pub use knownhosts::{KnownHosts, Hosts, Host};
pub use listener::Listener;
pub use session::Session;
-pub use sftp::{Sftp};
+pub use sftp::{Sftp, OpenFlags, Read, Write, Append, Create, Truncate};
+pub use sftp::{Exclusive, OpenType, OpenFile, OpenDir, File, FileStat};
+pub use sftp::{RenameFlags, Atomic, Overwrite, Native};
mod agent;
mod channel;
diff --git a/src/sftp.rs b/src/sftp.rs
index 08391fa..dfaf5ff 100644
--- a/src/sftp.rs
+++ b/src/sftp.rs
@@ -1,5 +1,7 @@
use std::kinds::marker;
-use libc::c_int;
+use std::mem;
+use std::io;
+use libc::{c_int, c_ulong, c_long, c_uint, size_t};
use {raw, Session, Error};
@@ -10,6 +12,58 @@ pub struct Sftp<'a> {
marker3: marker::NoSend,
}
+pub struct File<'a> {
+ raw: *mut raw::LIBSSH2_SFTP_HANDLE,
+ sftp: &'a Sftp<'a>,
+ marker: marker::NoSync,
+}
+
+pub struct FileStat {
+ pub size: Option<u64>,
+ pub uid: Option<uint>,
+ pub gid: Option<uint>,
+ pub perm: Option<uint>,
+ pub atime: Option<uint>,
+ pub mtime: Option<uint>,
+}
+
+bitflags! {
+ flags OpenFlags: c_ulong {
+ #[doc = "Open the file for reading."]
+ static Read = raw::LIBSSH2_FXF_READ,
+ #[doc = "Open the file for writing. If both this and Read are \
+ specified, the file is opened for both reading and writing"]
+ static Write = raw::LIBSSH2_FXF_WRITE,
+ #[doc = "Force all writes to append data at the end of the file."]
+ static Append = raw::LIBSSH2_FXF_APPEND,
+ #[doc = "If this flag is specified, then a new file will be created if \
+ one does not already exist (if Truncate is specified, the new \
+ file will be truncated to zero length if it previously \
+ exists) "]
+ static Create = raw::LIBSSH2_FXF_CREAT,
+ #[doc = "Forces an existing file with the same name to be truncated to \
+ zero length when creating a file by specifying `Create`. \
+ Using this flag implies the `Create` flag."]
+ static Truncate = raw::LIBSSH2_FXF_TRUNC | Create.bits,
+ #[doc = "Causes the request to fail if the named file already exists. \
+ Using this flag implies the `Create` flag."]
+ static Exclusive = raw::LIBSSH2_FXF_EXCL | Create.bits
+ }
+}
+
+bitflags! {
+ flags RenameFlags: c_long {
+ static Overwrite = raw::LIBSSH2_SFTP_RENAME_OVERWRITE,
+ static Atomic = raw::LIBSSH2_SFTP_RENAME_ATOMIC,
+ static Native = raw::LIBSSH2_SFTP_RENAME_NATIVE
+ }
+}
+
+pub enum OpenType {
+ OpenFile = raw::LIBSSH2_SFTP_OPENFILE as int,
+ OpenDir = raw::LIBSSH2_SFTP_OPENDIR as int,
+}
+
impl<'a> Sftp<'a> {
/// Wraps a raw pointer in a new Sftp structure tied to the lifetime of the
/// given session.
@@ -25,11 +79,218 @@ impl<'a> Sftp<'a> {
}
}
+ /// Open a handle to a file.
+ pub fn open_mode(&self, filename: &Path, flags: OpenFlags,
+ mode: io::FilePermission,
+ open_type: OpenType) -> Result<File, Error> {
+ let filename = filename.as_vec();
+ unsafe {
+ let ret = raw::libssh2_sftp_open_ex(self.raw,
+ filename.as_ptr() as *const _,
+ filename.len() as c_uint,
+ flags.bits() as c_ulong,
+ mode.bits() as c_long,
+ open_type as c_int);
+ if ret.is_null() {
+ Err(self.last_error())
+ } else {
+ Ok(File::from_raw(self, ret))
+ }
+ }
+ }
+
+ /// Helper to open a file in the `Read` mode.
+ pub fn open(&self, filename: &Path) -> Result<File, Error> {
+ self.open_mode(filename, Read, io::UserFile, OpenFile)
+ }
+
+ /// Helper to create a file in write-only mode with truncation.
+ pub fn create(&self, filename: &Path) -> Result<File, Error> {
+ self.open_mode(filename, Write | Truncate, io::UserFile, OpenFile)
+ }
+
+ /// Helper to open a directory for reading its contents.
+ pub fn opendir(&self, dirname: &Path) -> Result<File, Error> {
+ self.open_mode(dirname, Read, io::UserFile, OpenDir)
+ }
+
+ /// Convenience function to read the files in a directory.
+ ///
+ /// The returned paths are all joined with `dirname` when returned, and the
+ /// paths `.` and `..` are filtered out of the returned list.
+ pub fn readdir(&self, dirname: &Path)
+ -> Result<Vec<(Path, FileStat)>, Error> {
+ let mut dir = try!(self.opendir(dirname));
+ let mut ret = Vec::new();
+ loop {
+ match dir.readdir() {
+ Ok((filename, stat)) => {
+ if filename.as_vec() == b"." ||
+ filename.as_vec() == b".." { continue }
+
+ ret.push((dirname.join(filename), stat))
+ }
+ Err(ref e) if e.code() == raw::LIBSSH2_ERROR_FILE => break,
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(ret)
+ }
+
+ /// Create a directory on the remote file system.
+ pub fn mkdir(&self, filename: &Path, mode: io::FilePermission)
+ -> Result<(), Error> {
+ let filename = filename.as_vec();
+ self.rc(unsafe {
+ raw::libssh2_sftp_mkdir_ex(self.raw,
+ filename.as_ptr() as *const _,
+ filename.len() as c_uint,
+ mode.bits() as c_long)
+ })
+ }
+
+ /// Remove a directory from the remote file system.
+ pub fn rmdir(&self, filename: &Path) -> Result<(), Error> {
+ let filename = filename.as_vec();
+ self.rc(unsafe {
+ raw::libssh2_sftp_rmdir_ex(self.raw,
+ filename.as_ptr() as *const _,
+ filename.len() as c_uint)
+ })
+ }
+
+ /// Get the metadata for a file, performed by stat(2)
+ pub fn stat(&self, filename: &Path) -> Result<FileStat, Error> {
+ let filename = filename.as_vec();
+ unsafe {
+ let mut ret = mem::zeroed();
+ let rc = raw::libssh2_sftp_stat_ex(self.raw,
+ filename.as_ptr() as *const _,
+ filename.len() as c_uint,
+ raw::LIBSSH2_SFTP_STAT,
+ &mut ret);
+ try!(self.rc(rc));
+ Ok(FileStat::from_raw(&ret))
+ }
+ }
+
+ /// Get the metadata for a file, performed by lstat(2)
+ pub fn lstat(&self, filename: &Path) -> Result<FileStat, Error> {
+ let filename = filename.as_vec();
+ unsafe {
+ let mut ret = mem::zeroed();
+ let rc = raw::libssh2_sftp_stat_ex(self.raw,
+ filename.as_ptr() as *const _,
+ filename.len() as c_uint,
+ raw::LIBSSH2_SFTP_LSTAT,
+ &mut ret);
+ try!(self.rc(rc));
+ Ok(FileStat::from_raw(&ret))
+ }
+ }
+
+ /// Set the metadata for a file.
+ pub fn setstat(&self, filename: &Path, stat: FileStat) -> Result<(), Error> {
+ let filename = filename.as_vec();
+ self.rc(unsafe {
+ let mut raw = stat.raw();
+ raw::libssh2_sftp_stat_ex(self.raw,
+ filename.as_ptr() as *const _,
+ filename.len() as c_uint,
+ raw::LIBSSH2_SFTP_SETSTAT,
+ &mut raw)
+ })
+ }
+
+ /// Create a symlink at `target` pointing at `path`.
+ pub fn symlink(&self, path: &Path, target: &Path) -> Result<(), Error> {
+ let path = path.as_vec();
+ let target = target.as_vec();
+ self.rc(unsafe {
+ raw::libssh2_sftp_symlink_ex(self.raw,
+ path.as_ptr() as *const _,
+ path.len() as c_uint,
+ target.as_ptr() as *mut _,
+ target.len() as c_uint,
+ raw::LIBSSH2_SFTP_SYMLINK)
+ })
+ }
+
+ /// Read a symlink at `path`.
+ pub fn readlink(&self, path: &Path) -> Result<Path, Error> {
+ self.readlink_op(path, raw::LIBSSH2_SFTP_READLINK)
+ }
+
+ /// Resolve the real path for `path`.
+ pub fn realpath(&self, path: &Path) -> Result<Path, Error> {
+ self.readlink_op(path, raw::LIBSSH2_SFTP_REALPATH)
+ }
+
+ fn readlink_op(&self, path: &Path, op: c_int) -> Result<Path, Error> {
+ let path = path.as_vec();
+ let mut ret = Vec::<u8>::with_capacity(128);
+ let mut rc;
+ loop {
+ rc = unsafe {
+ raw::libssh2_sftp_symlink_ex(self.raw,
+ path.as_ptr() as *const _,
+ path.len() as c_uint,
+ ret.as_ptr() as *mut _,
+ ret.capacity() as c_uint,
+ op)
+ };
+ if rc == raw::LIBSSH2_ERROR_BUFFER_TOO_SMALL {
+ let cap = ret.capacity();
+ ret.reserve_additional(cap);
+ } else {
+ break
+ }
+ }
+ if rc < 0 {
+ Err(self.last_error())
+ } else {
+ unsafe { ret.set_len(rc as uint) }
+ Ok(Path::new(ret))
+ }
+ }
+
+ /// Rename a filesystem object on the remote filesystem.
+ ///
+ /// The semantics of this command typically include the ability to move a
+ /// filesystem object between folders and/or filesystem mounts. If the
+ /// `Overwrite` flag is not set and the destfile entry already exists, the
+ /// operation will fail.
+ ///
+ /// Use of the other flags (Native or Atomic) indicate a preference (but
+ /// not a requirement) for the remote end to perform an atomic rename
+ /// operation and/or using native system calls when possible.
+ ///
+ /// If no flags are specified then all flags are used.
+ pub fn rename(&self, src: &Path, dst: &Path, flags: Option<RenameFlags>)
+ -> Result<(), Error> {
+ let flags = flags.unwrap_or(Atomic | Overwrite | Native);
+ let src = src.as_vec();
+ let dst = dst.as_vec();
+ self.rc(unsafe {
+ raw::libssh2_sftp_rename_ex(self.raw,
+ src.as_ptr() as *const _,
+ src.len() as c_uint,
+ dst.as_ptr() as *const _,
+ dst.len() as c_uint,
+ flags.bits())
+ })
+ }
+
/// Peel off the last error to happen on this SFTP instance.
pub fn last_error(&self) -> Error {
let code = unsafe { raw::libssh2_sftp_last_error(self.raw) };
Error::from_errno(code as c_int)
}
+
+ /// Translates a return code into a Rust-`Result`
+ pub fn rc(&self, rc: c_int) -> Result<(), Error> {
+ if rc == 0 {Ok(())} else {Err(self.last_error())}
+ }
}
#[unsafe_destructor]
@@ -39,4 +300,222 @@ impl<'a> Drop for Sftp<'a> {
}
}
+impl<'a> File<'a> {
+ /// Wraps a raw pointer in a new File structure tied to the lifetime of the
+ /// given session.
+ ///
+ /// This consumes ownership of `raw`.
+ pub unsafe fn from_raw(sftp: &'a Sftp<'a>,
+ raw: *mut raw::LIBSSH2_SFTP_HANDLE) -> File<'a> {
+ File {
+ raw: raw,
+ sftp: sftp,
+ marker: marker::NoSync,
+ }
+ }
+
+ /// Set the metadata for this handle.
+ pub fn setstat(&mut self, stat: FileStat) -> Result<(), Error> {
+ self.sftp.rc(unsafe {
+ let mut raw = stat.raw();
+ raw::libssh2_sftp_fstat_ex(self.raw, &mut raw, 1)
+ })
+ }
+
+ /// Get the metadata for this handle.
+ pub fn stat(&mut self) -> Result<FileStat, Error> {
+ unsafe {
+ let mut ret = mem::zeroed();
+ try!(self.sftp.rc(raw::libssh2_sftp_fstat_ex(self.raw, &mut ret, 0)));
+ Ok(FileStat::from_raw(&ret))
+ }
+ }
+
+ pub fn statvfs(&mut self) -> Result<raw::LIBSSH2_SFTP_STATVFS, Error> {
+ unsafe {
+ let mut ret = mem::zeroed();
+ try!(self.sftp.rc(raw::libssh2_sftp_fstatvfs(self.raw, &mut ret)));
+ Ok(ret)
+ }
+ }
+
+ /// Reads a block of data from a handle and returns file entry information
+ /// for the next entry, if any.
+ ///
+ /// Note that this provides raw access to the `readdir` function from
+ /// libssh2. This will return an error when there are no more files to
+ /// read, and files such as `.` and `..` will be included in the return
+ /// values.
+ ///
+ /// Also note that the return paths will not be absolute paths, they are
+ /// the filenames of the files in this directory.
+ pub fn readdir(&mut self) -> Result<(Path, FileStat), Error> {
+ let mut buf = Vec::<u8>::with_capacity(128);
+ let mut stat = unsafe { mem::zeroed() };
+ let mut rc;
+ loop {
+ rc = unsafe {
+ raw::libssh2_sftp_readdir_ex(self.raw,
+ buf.as_mut_ptr() as *mut _,
+ buf.capacity() as size_t,
+ 0 as *mut _, 0,
+ &mut stat)
+ };
+ if rc == raw::LIBSSH2_ERROR_BUFFER_TOO_SMALL {
+ let cap = buf.capacity();
+ buf.reserve_additional(cap);
+ } else {
+ break
+ }
+ }
+ if rc < 0 {
+ return Err(self.sftp.last_error())
+ } else if rc == 0 {
+ return Err(Error::new(raw::LIBSSH2_ERROR_FILE, "no more files"))
+ } else {
+ unsafe { buf.set_len(rc as uint); }
+ }
+ Ok((Path::new(buf), FileStat::from_raw(&stat)))
+ }
+
+ /// This function causes the remote server to synchronize the file data and
+ /// metadata to disk (like fsync(2)).
+ ///
+ /// For this to work requires fsync@openssh.com support on the server.
+ pub fn fsync(&mut self) -> Result<(), Error> {
+ self.sftp.rc(unsafe { raw::libssh2_sftp_fsync(self.raw) })
+ }
+}
+
+impl<'a> Reader for File<'a> {
+ fn read(&mut self, buf: &mut [u8]) -> io::IoResult<uint> {
+ unsafe {
+ let rc = raw::libssh2_sftp_read(self.raw,
+ buf.as_mut_ptr() as *mut _,
+ buf.len() as size_t);
+ match rc {
+ 0 => Err(io::standard_error(io::EndOfFile)),
+ n if n < 0 => Err(io::IoError {
+ kind: io::OtherIoError,
+ desc: "read error",
+ detail: Some(self.sftp.last_error().to_string()),
+ }),
+ n => Ok(n as uint)
+ }
+ }
+ }
+}
+
+impl<'a> Writer for File<'a> {
+ fn write(&mut self, mut buf: &[u8]) -> io::IoResult<()> {
+ while buf.len() > 0 {
+ let rc = unsafe {
+ raw::libssh2_sftp_write(self.raw,
+ buf.as_ptr() as *const _,
+ buf.len() as size_t)
+ };
+ if rc < 0 {
+ return Err(io::IoError {
+ kind: io::OtherIoError,
+ desc: "write error",
+ detail: Some(self.sftp.last_error().to_string()),
+ })
+ }
+ buf = buf.slice_from(rc as uint);
+ }
+ Ok(())
+ }
+}
+
+impl<'a> Seek for File<'a> {
+ fn tell(&self) -> io::IoResult<u64> {
+ Ok(unsafe { raw::libssh2_sftp_tell64(self.raw) })
+ }
+ /// Move the file handle's internal pointer to an arbitrary location.
+ ///
+ /// libssh2 implements file pointers as a localized concept to make file
+ /// access appear more POSIX like. No packets are exchanged with the server
+ /// during a seek operation. The localized file pointer is simply used as a
+ /// convenience offset during read/write operations.
+ ///
+ /// You MUST NOT seek during writing or reading a file with SFTP, as the
+ /// internals use outstanding packets and changing the "file position"
+ /// during transit will results in badness.
+ fn seek(&mut self, offset: i64, whence: io::SeekStyle) -> io::IoResult<()> {
+ let next = match whence {
+ io::SeekSet => offset as u64,
+ io::SeekCur => (self.tell().unwrap() as i64 + offset) as u64,
+ io::SeekEnd => match self.stat() {
+ Ok(s) => match s.size {
+ Some(size) => (size as i64 + offset) as u64,
+ None => return Err(io::IoError {
+ kind: io::OtherIoError,
+ desc: "no file size available",
+ detail: None,
+ })
+ },
+ Err(e) => return Err(io::IoError {
+ kind: io::OtherIoError,
+ desc: "failed to stat remote file",
+ detail: Some(e.to_string()),
+ }),
+ }
+ };
+ unsafe { raw::libssh2_sftp_seek64(self.raw, next) }
+ Ok(())
+ }
+}
+
+#[unsafe_destructor]
+impl<'a> Drop for File<'a> {
+ fn drop(&mut self) {
+ unsafe { assert_eq!(raw::libssh2_sftp_close_handle(self.raw), 0) }
+ }
+}
+
+impl FileStat {
+ /// Creates a new instance of a stat from a raw instance.
+ pub fn from_raw(raw: &raw::LIBSSH2_SFTP_ATTRIBUTES) -> FileStat {
+ fn val<T: Copy>(raw: &raw::LIBSSH2_SFTP_ATTRIBUTES, t: &T,
+ flag: c_ulong) -> Option<T> {
+ if raw.flags & flag != 0 {Some(*t)} else {None}
+ }
+
+ FileStat {
+ size: val(raw, &raw.filesize, raw::LIBSSH2_SFTP_ATTR_SIZE),
+ uid: val(raw, &raw.uid, raw::LIBSSH2_SFTP_ATTR_UIDGID)
+ .map(|s| s as uint),
+ gid: val(raw, &raw.gid, raw::LIBSSH2_SFTP_ATTR_UIDGID)
+ .map(|s| s as uint),
+ perm: val(raw, &raw.permissions, raw::LIBSSH2_SFTP_ATTR_PERMISSIONS)
+ .map(|s| s as uint),
+ mtime: val(raw, &raw.mtime, raw::LIBSSH2_SFTP_ATTR_ACMODTIME)
+ .map(|s| s as uint),
+ atime: val(raw, &raw.atime, raw::LIBSSH2_SFTP_ATTR_ACMODTIME)
+ .map(|s| s as uint),
+ }
+ }
+
+ /// Convert this stat structure to its raw representation.
+ pub fn raw(&self) -> raw::LIBSSH2_SFTP_ATTRIBUTES {
+ fn flag<T>(o: &Option<T>, flag: c_ulong) -> c_ulong {
+ if o.is_some() {flag} else {0}
+ }
+
+ raw::LIBSSH2_SFTP_ATTRIBUTES {
+ flags: flag(&self.size, raw::LIBSSH2_SFTP_ATTR_SIZE) |
+ flag(&self.uid, raw::LIBSSH2_SFTP_ATTR_UIDGID) |
+ flag(&self.gid, raw::LIBSSH2_SFTP_ATTR_UIDGID) |
+ flag(&self.perm, raw::LIBSSH2_SFTP_ATTR_PERMISSIONS) |
+ flag(&self.atime, raw::LIBSSH2_SFTP_ATTR_ACMODTIME) |
+ flag(&self.mtime, raw::LIBSSH2_SFTP_ATTR_ACMODTIME),
+ filesize: self.size.unwrap_or(0),
+ uid: self.uid.unwrap_or(0) as c_ulong,
+ gid: self.gid.unwrap_or(0) as c_ulong,
+ permissions: self.perm.unwrap_or(0) as c_ulong,
+ atime: self.atime.unwrap_or(0) as c_ulong,
+ mtime: self.mtime.unwrap_or(0) as c_ulong,
+ }
+ }
+}
diff --git a/tests/sftp.rs b/tests/sftp.rs
index f600abd..1abf02b 100644
--- a/tests/sftp.rs
+++ b/tests/sftp.rs
@@ -1,5 +1,40 @@
+use std::io::{mod, fs, File, TempDir};
+use std::io::fs::PathExtensions;
+
#[test]
fn smoke() {
let (_tcp, sess) = ::authed_session();
sess.sftp().unwrap();
}
+
+#[test]
+fn ops() {
+ let td = TempDir::new("foo").unwrap();
+ File::create(&td.path().join("foo")).unwrap();
+ fs::mkdir(&td.path().join("bar"), io::UserDir).unwrap();
+
+ let (_tcp, sess) = ::authed_session();
+ let sftp = sess.sftp().unwrap();
+ sftp.opendir(&td.path().join("bar")).unwrap();
+ let mut foo = sftp.open(&td.path().join("foo")).unwrap();
+ sftp.mkdir(&td.path().join("bar2"), io::UserDir).unwrap();
+ assert!(td.path().join("bar2").is_dir());
+ sftp.rmdir(&td.path().join("bar2")).unwrap();
+
+ sftp.create(&td.path().join("foo5")).unwrap().write(b"foo").unwrap();
+ assert_eq!(File::open(&td.path().join("foo5")).read_to_end().unwrap(),
+ b"foo".to_vec());
+
+ assert_eq!(sftp.stat(&td.path().join("foo")).unwrap().size, Some(0));
+ assert_eq!(foo.read_to_end().unwrap(), Vec::new());
+
+ sftp.symlink(&td.path().join("foo"),
+ &td.path().join("foo2")).unwrap();
+ let readlink = sftp.readlink(&td.path().join("foo2")).unwrap();
+ assert!(readlink == td.path().join("foo"));
+ let realpath = sftp.realpath(&td.path().join("foo2")).unwrap();
+ assert!(realpath == td.path().join("foo"));
+
+ let files = sftp.readdir(td.path()).unwrap();
+ assert_eq!(files.len(), 4);
+}