diff options
author | Alex Crichton <alex@alexcrichton.com> | 2014-09-19 17:34:02 -0700 |
---|---|---|
committer | Alex Crichton <alex@alexcrichton.com> | 2014-09-19 18:00:07 -0700 |
commit | b4a344e1cc48b8cf8e6aa9957b090e73f6f31646 (patch) | |
tree | d0c3910a1483d5f0f49e29add45d596888c2a380 | |
parent | fb3186e7285977d88babc4ce13bf93a6bc76ccb5 (diff) | |
download | ssh2-rs-b4a344e1cc48b8cf8e6aa9957b090e73f6f31646.zip |
Bind SFTP functionality.
-rw-r--r-- | libssh2-sys/lib.rs | 106 | ||||
-rw-r--r-- | src/error.rs | 3 | ||||
-rw-r--r-- | src/lib.rs | 4 | ||||
-rw-r--r-- | src/sftp.rs | 481 | ||||
-rw-r--r-- | tests/sftp.rs | 35 |
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 { @@ -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); +} |