diff options
Diffstat (limited to 'src/sftp.rs')
-rw-r--r-- | src/sftp.rs | 481 |
1 files changed, 480 insertions, 1 deletions
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, + } + } +} |