summaryrefslogtreecommitdiff
path: root/src
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 /src
parentfb3186e7285977d88babc4ce13bf93a6bc76ccb5 (diff)
downloadssh2-rs-b4a344e1cc48b8cf8e6aa9957b090e73f6f31646.zip
Bind SFTP functionality.
Diffstat (limited to 'src')
-rw-r--r--src/error.rs3
-rw-r--r--src/lib.rs4
-rw-r--r--src/sftp.rs481
3 files changed, 486 insertions, 2 deletions
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,
+ }
+ }
+}