diff options
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | libssh2-sys/Cargo.toml | 4 | ||||
-rw-r--r-- | libssh2-sys/build.rs | 61 | ||||
-rw-r--r-- | src/channel.rs | 52 | ||||
-rw-r--r-- | src/knownhosts.rs | 13 | ||||
-rw-r--r-- | src/lib.rs | 36 | ||||
-rw-r--r-- | src/session.rs | 140 | ||||
-rw-r--r-- | src/sftp.rs | 157 | ||||
-rw-r--r-- | src/util.rs | 28 | ||||
-rw-r--r-- | tests/all.rs | 5 | ||||
-rw-r--r-- | tests/channel.rs | 29 | ||||
-rw-r--r-- | tests/session.rs | 23 | ||||
-rw-r--r-- | tests/sftp.rs | 19 | ||||
-rw-r--r-- | tests/tempdir.rs | 102 |
14 files changed, 420 insertions, 254 deletions
@@ -1,6 +1,6 @@ [package] name = "ssh2" -version = "0.1.11" +version = "0.2.0" authors = ["Alex Crichton <alex@alexcrichton.com>"] license = "MIT/Apache-2.0" keywords = ["ssh"] @@ -23,3 +23,6 @@ libc = "0.1" [dependencies.libssh2-sys] path = "libssh2-sys" version = "0.1.0" + +[dev-dependencies] +rand = "0.1" diff --git a/libssh2-sys/Cargo.toml b/libssh2-sys/Cargo.toml index a91df02..b247499 100644 --- a/libssh2-sys/Cargo.toml +++ b/libssh2-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libssh2-sys" -version = "0.1.8" +version = "0.1.9" authors = ["Alex Crichton <alex@alexcrichton.com>"] links = "ssh2" build = "build.rs" @@ -36,4 +36,4 @@ openssl-sys = "0.4.0" openssl-sys = "0.4.0" [build-dependencies] -pkg-config = "0.2" +pkg-config = "0.3" diff --git a/libssh2-sys/build.rs b/libssh2-sys/build.rs index 9cab3b0..38f24be 100644 --- a/libssh2-sys/build.rs +++ b/libssh2-sys/build.rs @@ -1,11 +1,12 @@ -#![feature(old_io, old_path, env, core)] +#![feature(io, path, env, core, fs, process, old_path)] extern crate "pkg-config" as pkg_config; use std::env; -use std::old_io::{self, fs, Command}; -use std::old_io::process::InheritFd; -use std::old_io::fs::PathExtensions; +use std::fs; +use std::io::prelude::*; +use std::path::PathBuf; +use std::process::Command; fn main() { match pkg_config::find_library("libssh2") { @@ -35,8 +36,8 @@ fn main() { Err(..) => {} } - let src = Path::new(env::var("CARGO_MANIFEST_DIR").unwrap()); - let dst = Path::new(env::var("OUT_DIR").unwrap()); + let src = PathBuf::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()); + let dst = PathBuf::new(&env::var_os("OUT_DIR").unwrap()); let mut config_opts = Vec::new(); if windows { @@ -47,10 +48,10 @@ fn main() { config_opts.push("--disable-examples-build".to_string()); config_opts.push(format!("--prefix={}", dst.display())); - let _ = fs::rmdir_recursive(&dst.join("include")); - let _ = fs::rmdir_recursive(&dst.join("lib")); - let _ = fs::rmdir_recursive(&dst.join("build")); - fs::mkdir(&dst.join("build"), old_io::USER_DIR).unwrap(); + let _ = fs::remove_dir_all(&dst.join("include")); + let _ = fs::remove_dir_all(&dst.join("lib")); + let _ = fs::remove_dir_all(&dst.join("build")); + fs::create_dir(&dst.join("build")).unwrap(); let root = src.join("libssh2-1.4.4-20140901"); // Can't run ./configure directly on msys2 b/c we're handing in @@ -61,21 +62,21 @@ fn main() { // Also apparently the buildbots choke unless we manually set LD, who knows // why?! run(Command::new("sh") - .env("CFLAGS", cflags) - .env("LD", which("ld").unwrap()) - .cwd(&dst.join("build")) + .env("CFLAGS", &cflags) + .env("LD", &which("ld").unwrap()) + .current_dir(&dst.join("build")) .arg("-c") - .arg(format!("{} {}", root.join("configure").display(), - config_opts.connect(" ")) - .replace("C:\\", "/c/") - .replace("\\", "/"))); - run(Command::new(make()) - .arg(format!("-j{}", env::var("NUM_JOBS").unwrap())) - .cwd(&dst.join("build/src"))); + .arg(&format!("{} {}", root.join("configure").display(), + config_opts.connect(" ")) + .replace("C:\\", "/c/") + .replace("\\", "/"))); + run(Command::new(&make()) + .arg(&format!("-j{}", env::var("NUM_JOBS").unwrap())) + .current_dir(&dst.join("build/src"))); // Don't run `make install` because apparently it's a little buggy on mingw // for windows. - fs::mkdir_recursive(&dst.join("lib/pkgconfig"), old_io::USER_DIR).unwrap(); + fs::create_dir_all(&dst.join("lib/pkgconfig")).unwrap(); // Which one does windows generate? Who knows! let p1 = dst.join("build/src/.libs/libssh2.a"); @@ -92,11 +93,12 @@ fn main() { let root = root.join("include"); let dst = dst.join("include"); for file in fs::walk_dir(&root).unwrap() { - if fs::stat(&file).unwrap().kind != old_io::FileType::RegularFile { continue } + let file = file.unwrap().path(); + if !file.is_file() { continue } - let part = file.path_relative_from(&root).unwrap(); + let part = file.relative_from(&root).unwrap(); let dst = dst.join(part); - fs::mkdir_recursive(&dst.dir_path(), old_io::USER_DIR).unwrap(); + fs::create_dir_all(dst.parent().unwrap()).unwrap(); fs::copy(&file, &dst).unwrap(); } } @@ -115,17 +117,14 @@ fn make() -> &'static str { fn run(cmd: &mut Command) { println!("running: {:?}", cmd); - assert!(cmd.stdout(InheritFd(1)) - .stderr(InheritFd(2)) - .status() - .unwrap() - .success()); - + assert!(cmd.status().unwrap().success()); } -fn which(cmd: &str) -> Option<Path> { +fn which(cmd: &str) -> Option<PathBuf> { let cmd = format!("{}{}", cmd, env::consts::EXE_SUFFIX); env::split_paths(&env::var("PATH").unwrap()).map(|p| { p.join(&cmd) + }).map(|p| { + PathBuf::new(p.as_str().unwrap()) }).find(|p| p.exists()) } diff --git a/src/channel.rs b/src/channel.rs index e415af7..a27afa1 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,5 +1,6 @@ +use std::io::prelude::*; +use std::io::{self, ErrorKind}; use std::cmp; -use std::old_io; use libc::{c_uint, c_int, size_t, c_char, c_void, c_uchar}; use {raw, Session, Error}; @@ -119,11 +120,14 @@ impl<'sess> Channel<'sess> { /// # Example /// /// ```no_run + /// # use std::io::prelude::*; /// # use ssh2::Session; /// # let session: Session = panic!(); /// let mut channel = session.channel_session().unwrap(); /// channel.exec("ls").unwrap(); - /// println!("{}", channel.read_to_string().unwrap()); + /// let mut s = String::new(); + /// channel.read_to_string(&mut s).unwrap(); + /// println!("{}", s); /// ``` pub fn exec(&mut self, command: &str) -> Result<(), Error> { self.process_startup("exec", Some(command)) @@ -181,7 +185,7 @@ impl<'sess> Channel<'sess> { } /// Write data to the channel stderr stream. - pub fn write_stderr(&mut self, data: &[u8]) -> Result<(), Error> { + pub fn write_stderr(&mut self, data: &[u8]) -> Result<usize, Error> { self.write_stream(::EXTENDED_DATA_STDERR, data) } @@ -192,13 +196,13 @@ impl<'sess> Channel<'sess> { /// selected stream_id. The SSH2 protocol currently defines a stream ID of 1 /// to be the stderr substream. pub fn write_stream(&mut self, stream_id: i32, data: &[u8]) - -> Result<(), Error> { + -> Result<usize, Error> { unsafe { let rc = raw::libssh2_channel_write_ex(self.raw, stream_id as c_int, data.as_ptr() as *mut _, data.len() as size_t); - self.sess.rc(rc) + self.sess.rc(rc).map(|()| rc as usize) } } @@ -215,7 +219,7 @@ impl<'sess> Channel<'sess> { /// to be the stderr substream. pub fn read_stream(&mut self, stream_id: i32, data: &mut [u8]) -> Result<usize, Error> { - if self.eof() { return Err(Error::eof()) } + if self.eof() { return Ok(0) } let data = match self.read_limit { Some(amt) => { @@ -230,7 +234,6 @@ impl<'sess> Channel<'sess> { data.as_mut_ptr() as *mut _, data.len() as size_t); if rc < 0 { try!(self.sess.rc(rc)); } - if rc == 0 && self.eof() { return Err(Error::eof()) } match self.read_limit { Some(ref mut amt) => *amt -= rc as u64, None => {} @@ -397,40 +400,27 @@ impl<'sess> SessionBinding<'sess> for Channel<'sess> { fn raw(&self) -> *mut raw::LIBSSH2_CHANNEL { self.raw } } -impl<'sess> Writer for Channel<'sess> { - fn write_all(&mut self, buf: &[u8]) -> old_io::IoResult<()> { +impl<'sess> Write for Channel<'sess> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.write_stream(0, buf).map_err(|e| { - old_io::IoError { - kind: old_io::OtherIoError, - desc: "ssh write error", - detail: Some(e.to_string()), - } + io::Error::new(ErrorKind::Other, "ssh write error", + Some(e.to_string())) }) } - fn flush(&mut self) -> old_io::IoResult<()> { + fn flush(&mut self) -> io::Result<()> { self.flush_stream(0).map_err(|e| { - old_io::IoError { - kind: old_io::OtherIoError, - desc: "ssh write error", - detail: Some(e.to_string()), - } + io::Error::new(ErrorKind::Other, "ssh write error", + Some(e.to_string())) }) } } -impl<'sess> Reader for Channel<'sess> { - fn read(&mut self, buf: &mut [u8]) -> old_io::IoResult<usize> { +impl<'sess> Read for Channel<'sess> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.read_stream(0, buf).map_err(|e| { - if self.eof() { - old_io::standard_error(old_io::EndOfFile) - } else { - old_io::IoError { - kind: old_io::OtherIoError, - desc: "ssh read error", - detail: Some(e.to_string()), - } - } + io::Error::new(ErrorKind::Other, "ssh read error", + Some(e.to_string())) }) } } diff --git a/src/knownhosts.rs b/src/knownhosts.rs index 171c758..20d18a1 100644 --- a/src/knownhosts.rs +++ b/src/knownhosts.rs @@ -1,10 +1,11 @@ use std::ffi::CString; use std::marker; +use std::path::Path; use std::str; use libc::{c_int, size_t}; use {raw, Session, Error, KnownHostFileKind, CheckResult}; -use util::{Binding, SessionBinding}; +use util::{self, Binding, SessionBinding}; /// A set of known hosts which can be used to verify the identity of a remote /// server. @@ -12,8 +13,8 @@ use util::{Binding, SessionBinding}; /// # Example /// /// ```no_run -/// # #![allow(unstable)] -/// use std::os; +/// use std::env; +/// use std::path::Path; /// use ssh2::{self, CheckResult, HostKeyType, KnownHostKeyFormat}; /// use ssh2::KnownHostFileKind; /// @@ -21,7 +22,7 @@ use util::{Binding, SessionBinding}; /// let mut known_hosts = session.known_hosts().unwrap(); /// /// // Initialize the known hosts with a global known hosts file -/// let file = Path::new(os::getenv("HOME").unwrap()).join(".ssh/known_hosts"); +/// let file = Path::new(&env::var("HOME").unwrap()).join(".ssh/known_hosts"); /// known_hosts.read_file(&file, KnownHostFileKind::OpenSSH).unwrap(); /// /// // Now check to see if the seesion's host key is anywhere in the known @@ -68,7 +69,7 @@ impl<'sess> KnownHosts<'sess> { /// the collection of known hosts. pub fn read_file(&mut self, file: &Path, kind: KnownHostFileKind) -> Result<u32, Error> { - let file = try!(CString::new(file.as_vec())); + let file = try!(CString::new(try!(util::path2bytes(file)))); let n = unsafe { raw::libssh2_knownhost_readfile(self.raw, file.as_ptr(), kind as c_int) @@ -92,7 +93,7 @@ impl<'sess> KnownHosts<'sess> { /// file format. pub fn write_file(&self, file: &Path, kind: KnownHostFileKind) -> Result<(), Error> { - let file = try!(CString::new(file.as_vec())); + let file = try!(CString::new(try!(util::path2bytes(file)))); let n = unsafe { raw::libssh2_knownhost_writefile(self.raw, file.as_ptr(), kind as c_int) @@ -32,7 +32,7 @@ //! ## Authenticating with ssh-agent //! //! ```no_run -//! use std::old_io::TcpStream; +//! use std::net::TcpStream; //! use ssh2::Session; //! //! // Connect to the local SSH server @@ -50,7 +50,7 @@ //! ## Authenticating with a password //! //! ```no_run -//! use std::old_io::TcpStream; +//! use std::net::TcpStream; //! use ssh2::Session; //! //! // Connect to the local SSH server @@ -65,7 +65,8 @@ //! ## Run a command //! //! ```no_run -//! use std::old_io::{self, TcpStream}; +//! use std::io::prelude::*; +//! use std::net::{TcpStream}; //! use ssh2::Session; //! //! // Connect to the local SSH server @@ -76,14 +77,18 @@ //! //! let mut channel = sess.channel_session().unwrap(); //! channel.exec("ls").unwrap(); -//! println!("{}", channel.read_to_string().unwrap()); +//! let mut s = String::new(); +//! channel.read_to_string(&mut s).unwrap(); +//! println!("{}", s); //! println!("{}", channel.exit_status().unwrap()); //! ``` //! //! ## Upload a file //! //! ```no_run -//! use std::old_io::{self, TcpStream}; +//! use std::io::prelude::*; +//! use std::net::TcpStream; +//! use std::path::Path; //! use ssh2::Session; //! //! // Connect to the local SSH server @@ -92,15 +97,17 @@ //! sess.handshake(&tcp).unwrap(); //! sess.userauth_agent("username").unwrap(); //! -//! let mut remote_file = sess.scp_send(&Path::new("remote"), -//! old_io::USER_FILE, 10, None).unwrap(); +//! let mut remote_file = sess.scp_send(Path::new("remote"), +//! 0o644, 10, None).unwrap(); //! remote_file.write(b"1234567890").unwrap(); //! ``` //! //! ## Download a file //! //! ```no_run -//! use std::old_io::TcpStream; +//! use std::io::prelude::*; +//! use std::net::TcpStream; +//! use std::path::Path; //! use ssh2::Session; //! //! // Connect to the local SSH server @@ -109,13 +116,14 @@ //! sess.handshake(&tcp).unwrap(); //! sess.userauth_agent("username").unwrap(); //! -//! let (mut remote_file, stat) = sess.scp_recv(&Path::new("remote")).unwrap(); -//! println!("remote file size: {}", stat.size); -//! let contents = remote_file.read_to_end(); +//! let (mut remote_file, stat) = sess.scp_recv(Path::new("remote")).unwrap(); +//! println!("remote file size: {}", stat.size()); +//! let mut contents = Vec::new(); +//! remote_file.read_to_end(&mut contents).unwrap(); +//! // ... //! ``` -#![feature(unsafe_destructor, std_misc, collections, old_io, core, old_path)] -#![feature(io)] +#![feature(unsafe_destructor, std_misc, collections, io, core, path, os, net)] #![deny(missing_docs, unused_results)] #![cfg_attr(test, deny(warnings))] @@ -131,7 +139,7 @@ pub use channel::{Channel, ExitSignal, ReadWindow, WriteWindow}; pub use error::Error; pub use knownhosts::{KnownHosts, Hosts, Host}; pub use listener::Listener; -pub use session::Session; +pub use session::{Session, ScpFileStat}; pub use sftp::{Sftp, OpenFlags, READ, WRITE, APPEND, CREATE, TRUNCATE}; pub use sftp::{EXCLUSIVE, OpenType, File, FileStat}; pub use sftp::{RenameFlags, ATOMIC, OVERWRITE, NATIVE}; diff --git a/src/session.rs b/src/session.rs index 81ed002..c2dfc03 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,13 +1,14 @@ use std::ffi::CString; -use std::old_io::{self, TcpStream}; use std::mem; +use std::net::TcpStream; +use std::path::Path; use std::slice; use std::str; use libc::{self, c_uint, c_int, c_void, c_long}; use {raw, Error, DisconnectCode, ByApplication, HostKeyType}; use {MethodType, Agent, Channel, Listener, HashType, KnownHosts, Sftp}; -use util::{Binding, SessionBinding}; +use util::{self, Binding, SessionBinding}; /// An SSH session, typically representing one TCP connection. /// @@ -20,6 +21,11 @@ pub struct Session { unsafe impl Send for Session {} +/// Metadata returned about a remote file when received via `scp`. +pub struct ScpFileStat { + stat: libc::stat, +} + impl Session { /// Initializes an SSH session object. /// @@ -193,10 +199,10 @@ impl Session { privatekey: &Path, passphrase: Option<&str>) -> Result<(), Error> { let pubkey = match pubkey { - Some(s) => Some(try!(CString::new(s.as_vec()))), + Some(s) => Some(try!(CString::new(try!(util::path2bytes(s))))), None => None, }; - let privatekey = try!(CString::new(privatekey.as_vec())); + let privatekey = try!(CString::new(try!(util::path2bytes(privatekey)))); let passphrase = match passphrase { Some(s) => Some(try!(CString::new(s))), None => None, @@ -222,8 +228,8 @@ impl Session { hostname: &str, local_username: Option<&str>) -> Result<(), Error> { - let publickey = try!(CString::new(publickey.as_vec())); - let privatekey = try!(CString::new(privatekey.as_vec())); + let publickey = try!(CString::new(try!(util::path2bytes(publickey)))); + let privatekey = try!(CString::new(try!(util::path2bytes(privatekey)))); let passphrase = match passphrase { Some(s) => Some(try!(CString::new(s))), None => None, @@ -415,8 +421,8 @@ impl Session { /// sent over the returned channel. Some stat information is also returned /// about the remote file to prepare for receiving the file. pub fn scp_recv(&self, path: &Path) - -> Result<(Channel, old_io::FileStat), Error> { - let path = try!(CString::new(path.as_vec())); + -> Result<(Channel, ScpFileStat), Error> { + let path = try!(CString::new(try!(util::path2bytes(path)))); unsafe { let mut sb: libc::stat = mem::zeroed(); let ret = raw::libssh2_scp_recv(self.raw, path.as_ptr(), &mut sb); @@ -428,7 +434,7 @@ impl Session { // artificially limit the channel to a certain amount of bytes that // can be read. c.limit_read(sb.st_size as u64); - Ok((c, mkstat(&sb))) + Ok((c, ScpFileStat { stat: sb })) } } @@ -440,15 +446,15 @@ impl Session { /// /// The size of the file, `size`, must be known ahead of time before /// transmission. - pub fn scp_send(&self, remote_path: &Path, mode: old_io::FilePermission, + pub fn scp_send(&self, remote_path: &Path, mode: i32, size: u64, times: Option<(u64, u64)>) -> Result<Channel, Error> { - let path = try!(CString::new(remote_path.as_vec())); + let path = try!(CString::new(try!(util::path2bytes(remote_path)))); let (mtime, atime) = times.unwrap_or((0, 0)); unsafe { let ret = raw::libssh2_scp_send64(self.raw, path.as_ptr(), - mode.bits() as c_int, + mode as c_int, size, mtime as libc::time_t, atime as libc::time_t); @@ -618,57 +624,69 @@ impl Binding for Session { fn raw(&self) -> *mut raw::LIBSSH2_SESSION { self.raw } } -// Sure do wish this was exported in libnative! -fn mkstat(stat: &libc::stat) -> old_io::FileStat { - #[cfg(windows)] type Mode = libc::c_int; - #[cfg(unix)] type Mode = libc::mode_t; - - // FileStat times are in milliseconds - fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 } - - #[cfg(all(not(target_os = "linux"), not(target_os = "android")))] - fn flags(stat: &libc::stat) -> u64 { stat.st_flags as u64 } - #[cfg(any(target_os = "linux", target_os = "android"))] - fn flags(_stat: &libc::stat) -> u64 { 0 } - - #[cfg(all(not(target_os = "linux"), not(target_os = "android")))] - fn gen(stat: &libc::stat) -> u64 { stat.st_gen as u64 } - #[cfg(any(target_os = "linux", target_os = "android"))] - fn gen(_stat: &libc::stat) -> u64 { 0 } - - old_io::FileStat { - size: stat.st_size as u64, - kind: match (stat.st_mode as Mode) & libc::S_IFMT { - libc::S_IFREG => old_io::FileType::RegularFile, - libc::S_IFDIR => old_io::FileType::Directory, - libc::S_IFIFO => old_io::FileType::NamedPipe, - libc::S_IFBLK => old_io::FileType::BlockSpecial, - libc::S_IFLNK => old_io::FileType::Symlink, - _ => old_io::FileType::Unknown, - }, - perm: old_io::FilePermission::from_bits_truncate(stat.st_mode as u32), - created: mktime(stat.st_ctime as u64, stat.st_ctime_nsec as u64), - modified: mktime(stat.st_mtime as u64, stat.st_mtime_nsec as u64), - accessed: mktime(stat.st_atime as u64, stat.st_atime_nsec as u64), - unstable: old_io::UnstableFileStat { - device: stat.st_dev as u64, - inode: stat.st_ino as u64, - rdev: stat.st_rdev as u64, - nlink: stat.st_nlink as u64, - uid: stat.st_uid as u64, - gid: stat.st_gid as u64, - blksize: stat.st_blksize as u64, - blocks: stat.st_blocks as u64, - flags: flags(stat), - gen: gen(stat), - } +impl Drop for Session { + fn drop(&mut self) { + unsafe { assert_eq!(raw::libssh2_session_free(self.raw), 0); } } } -impl Drop for Session { - fn drop(&mut self) { - unsafe { - assert_eq!(raw::libssh2_session_free(self.raw), 0); - } +impl ScpFileStat { + /// Returns the size of the remote file. + pub fn size(&self) -> u64 { self.stat.st_size as u64 } + /// Returns the listed mode of the remote file. + pub fn mode(&self) -> i32 { self.stat.st_mode as i32 } + /// Returns whether the remote file is a directory. + pub fn is_dir(&self) -> bool { + self.mode() & (libc::S_IFMT as i32) == (libc::S_IFDIR as i32) + } + /// Returns whether the remote file is a regular file. + pub fn is_file(&self) -> bool { + self.mode() & (libc::S_IFMT as i32) == (libc::S_IFREG as i32) } } + +// fn mkstat(stat: &libc::stat) -> old_io::FileStat { +// #[cfg(windows)] type Mode = libc::c_int; +// #[cfg(unix)] type Mode = libc::mode_t; +// +// // FileStat times are in milliseconds +// fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 } +// +// #[cfg(all(not(target_os = "linux"), not(target_os = "android")))] +// fn flags(stat: &libc::stat) -> u64 { stat.st_flags as u64 } +// #[cfg(any(target_os = "linux", target_os = "android"))] +// fn flags(_stat: &libc::stat) -> u64 { 0 } +// +// #[cfg(all(not(target_os = "linux"), not(target_os = "android")))] +// fn gen(stat: &libc::stat) -> u64 { stat.st_gen as u64 } +// #[cfg(any(target_os = "linux", target_os = "android"))] +// fn gen(_stat: &libc::stat) -> u64 { 0 } +// +// old_io::FileStat { +// size: stat.st_size as u64, +// kind: match (stat.st_mode as Mode) & libc::S_IFMT { +// libc::S_IFREG => old_io::FileType::RegularFile, +// libc::S_IFDIR => old_io::FileType::Directory, +// libc::S_IFIFO => old_io::FileType::NamedPipe, +// libc::S_IFBLK => old_io::FileType::BlockSpecial, +// libc::S_IFLNK => old_io::FileType::Symlink, +// _ => old_io::FileType::Unknown, +// }, +// perm: old_io::FilePermission::from_bits_truncate(stat.st_mode as u32), +// created: mktime(stat.st_ctime as u64, stat.st_ctime_nsec as u64), +// modified: mktime(stat.st_mtime as u64, stat.st_mtime_nsec as u64), +// accessed: mktime(stat.st_atime as u64, stat.st_atime_nsec as u64), +// unstable: old_io::UnstableFileStat { +// device: stat.st_dev as u64, +// inode: stat.st_ino as u64, +// rdev: stat.st_rdev as u64, +// nlink: stat.st_nlink as u64, +// uid: stat.st_uid as u64, +// gid: stat.st_gid as u64, +// blksize: stat.st_blksize as u64, +// blocks: stat.st_blocks as u64, +// flags: flags(stat), +// gen: gen(stat), +// } +// } +// } diff --git a/src/sftp.rs b/src/sftp.rs index d39fc33..009f7cf 100644 --- a/src/sftp.rs +++ b/src/sftp.rs @@ -1,10 +1,12 @@ +use std::io::prelude::*; +use std::io::{self, ErrorKind, Seek, SeekFrom}; use std::marker; use std::mem; -use std::old_io; +use std::path::{Path, PathBuf}; use libc::{c_int, c_ulong, c_long, c_uint, size_t}; use {raw, Session, Error, Channel}; -use util::SessionBinding; +use util::{self, SessionBinding}; /// A handle to a remote filesystem over SFTP. /// @@ -98,15 +100,14 @@ pub enum OpenType { impl<'sess> Sftp<'sess> { /// Open a handle to a file. pub fn open_mode(&self, filename: &Path, flags: OpenFlags, - mode: old_io::FilePermission, - open_type: OpenType) -> Result<File, Error> { - let filename = filename.as_vec(); + mode: i32, open_type: OpenType) -> Result<File, Error> { + let filename = try!(util::path2bytes(filename)); 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, + mode as c_long, open_type as c_int); if ret.is_null() { Err(self.last_error()) @@ -118,17 +119,17 @@ impl<'sess> Sftp<'sess> { /// Helper to open a file in the `Read` mode. pub fn open(&self, filename: &Path) -> Result<File, Error> { - self.open_mode(filename, READ, old_io::USER_FILE, OpenType::File) + self.open_mode(filename, READ, 0o644, OpenType::File) } /// 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, old_io::USER_FILE, OpenType::File) + self.open_mode(filename, WRITE | TRUNCATE, 0o644, OpenType::File) } /// Helper to open a directory for reading its contents. pub fn opendir(&self, dirname: &Path) -> Result<File, Error> { - self.open_mode(dirname, READ, old_io::USER_FILE, OpenType::Dir) + self.open_mode(dirname, READ, 0, OpenType::Dir) } /// Convenience function to read the files in a directory. @@ -136,16 +137,16 @@ impl<'sess> Sftp<'sess> { /// 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> { + -> Result<Vec<(PathBuf, 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 } + if &*filename == Path::new(".") || + &*filename == Path::new("..") { continue } - ret.push((dirname.join(filename), stat)) + ret.push((dirname.join(&filename), stat)) } Err(ref e) if e.code() == raw::LIBSSH2_ERROR_FILE => break, Err(e) => return Err(e), @@ -155,20 +156,20 @@ impl<'sess> Sftp<'sess> { } /// Create a directory on the remote file system. - pub fn mkdir(&self, filename: &Path, mode: old_io::FilePermission) + pub fn mkdir(&self, filename: &Path, mode: i32) -> Result<(), Error> { - let filename = filename.as_vec(); + let filename = try!(util::path2bytes(filename)); 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) + mode as c_long) }) } /// Remove a directory from the remote file system. pub fn rmdir(&self, filename: &Path) -> Result<(), Error> { - let filename = filename.as_vec(); + let filename = try!(util::path2bytes(filename)); self.rc(unsafe { raw::libssh2_sftp_rmdir_ex(self.raw, filename.as_ptr() as *const _, @@ -178,7 +179,7 @@ impl<'sess> Sftp<'sess> { /// Get the metadata for a file, performed by stat(2) pub fn stat(&self, filename: &Path) -> Result<FileStat, Error> { - let filename = filename.as_vec(); + let filename = try!(util::path2bytes(filename)); unsafe { let mut ret = mem::zeroed(); let rc = raw::libssh2_sftp_stat_ex(self.raw, @@ -193,7 +194,7 @@ impl<'sess> Sftp<'sess> { /// Get the metadata for a file, performed by lstat(2) pub fn lstat(&self, filename: &Path) -> Result<FileStat, Error> { - let filename = filename.as_vec(); + let filename = try!(util::path2bytes(filename)); unsafe { let mut ret = mem::zeroed(); let rc = raw::libssh2_sftp_stat_ex(self.raw, @@ -208,7 +209,7 @@ impl<'sess> Sftp<'sess> { /// Set the metadata for a file. pub fn setstat(&self, filename: &Path, stat: FileStat) -> Result<(), Error> { - let filename = filename.as_vec(); + let filename = try!(util::path2bytes(filename)); self.rc(unsafe { let mut raw = stat.raw(); raw::libssh2_sftp_stat_ex(self.raw, @@ -221,8 +222,8 @@ impl<'sess> Sftp<'sess> { /// 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(); + let path = try!(util::path2bytes(path)); + let target = try!(util::path2bytes(target)); self.rc(unsafe { raw::libssh2_sftp_symlink_ex(self.raw, path.as_ptr() as *const _, @@ -234,17 +235,17 @@ impl<'sess> Sftp<'sess> { } /// Read a symlink at `path`. - pub fn readlink(&self, path: &Path) -> Result<Path, Error> { + pub fn readlink(&self, path: &Path) -> Result<PathBuf, Error> { self.readlink_op(path, raw::LIBSSH2_SFTP_READLINK) } /// Resolve the real path for `path`. - pub fn realpath(&self, path: &Path) -> Result<Path, Error> { + pub fn realpath(&self, path: &Path) -> Result<PathBuf, 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(); + fn readlink_op(&self, path: &Path, op: c_int) -> Result<PathBuf, Error> { + let path = try!(util::path2bytes(path)); let mut ret = Vec::<u8>::with_capacity(128); let mut rc; loop { @@ -267,7 +268,7 @@ impl<'sess> Sftp<'sess> { Err(self.last_error()) } else { unsafe { ret.set_len(rc as usize) } - Ok(Path::new(ret)) + Ok(mkpath(ret)) } } @@ -286,8 +287,8 @@ impl<'sess> Sftp<'sess> { 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(); + let src = try!(util::path2bytes(src)); + let dst = try!(util::path2bytes(dst)); self.rc(unsafe { raw::libssh2_sftp_rename_ex(self.raw, src.as_ptr() as *const _, @@ -377,7 +378,7 @@ impl<'sftp> File<'sftp> { /// /// 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> { + pub fn readdir(&mut self) -> Result<(PathBuf, FileStat), Error> { let mut buf = Vec::<u8>::with_capacity(128); let mut stat = unsafe { mem::zeroed() }; let mut rc; @@ -403,7 +404,7 @@ impl<'sftp> File<'sftp> { } else { unsafe { buf.set_len(rc as usize); } } - Ok((Path::new(buf), FileStat::from_raw(&stat))) + Ok((mkpath(buf), FileStat::from_raw(&stat))) } /// This function causes the remote server to synchronize the file data and @@ -415,51 +416,40 @@ impl<'sftp> File<'sftp> { } } -impl<'sftp> Reader for File<'sftp> { - fn read(&mut self, buf: &mut [u8]) -> old_io::IoResult<usize> { +impl<'sftp> Read for File<'sftp> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { unsafe { let rc = raw::libssh2_sftp_read(self.raw, buf.as_mut_ptr() as *mut _, buf.len() as size_t); match rc { - 0 => Err(old_io::standard_error(old_io::EndOfFile)), - n if n < 0 => Err(old_io::IoError { - kind: old_io::OtherIoError, - desc: "read error", - detail: Some(self.sftp.last_error().to_string()), - }), + n if n < 0 => Err(io::Error::new(ErrorKind::Other, "read error", + Some(self.sftp.last_error() + .to_string()))), n => Ok(n as usize) } } } } -impl<'sftp> Writer for File<'sftp> { - fn write_all(&mut self, mut buf: &[u8]) -> old_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(old_io::IoError { - kind: old_io::OtherIoError, - desc: "write error", - detail: Some(self.sftp.last_error().to_string()), - }) - } - buf = &buf[rc as usize..]; +impl<'sftp> Write for File<'sftp> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let rc = unsafe { + raw::libssh2_sftp_write(self.raw, + buf.as_ptr() as *const _, + buf.len() as size_t) + }; + if rc < 0 { + Err(io::Error::new(ErrorKind::Other, "write error", + Some(self.sftp.last_error().to_string()))) + } else { + Ok(rc as usize) } - Ok(()) } + fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl<'sftp> Seek for File<'sftp> { - fn tell(&self) -> old_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 @@ -470,28 +460,31 @@ impl<'sftp> Seek for File<'sftp> { /// 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: old_io::SeekStyle) -> old_io::IoResult<()> { - let next = match whence { - old_io::SeekSet => offset as u64, - old_io::SeekCur => (self.tell().unwrap() as i64 + offset) as u64, - old_io::SeekEnd => match self.stat() { + fn seek(&mut self, how: SeekFrom) -> io::Result<u64> { + let next = match how { + SeekFrom::Start(pos) => pos, + SeekFrom::Current(offset) => { + let cur = unsafe { raw::libssh2_sftp_tell64(self.raw) }; + (cur as i64 + offset) as u64 + } + SeekFrom::End(offset) => match self.stat() { Ok(s) => match s.size { Some(size) => (size as i64 + offset) as u64, - None => return Err(old_io::IoError { - kind: old_io::OtherIoError, - desc: "no file size available", - detail: None, - }) + None => { + return Err(io::Error::new(ErrorKind::Other, + "no file size available", + None)) + } }, - Err(e) => return Err(old_io::IoError { - kind: old_io::OtherIoError, - desc: "failed to stat remote file", - detail: Some(e.to_string()), - }), + Err(e) => { + return Err(io::Error::new(ErrorKind::Other, + "failed to stat remote file", + Some(e.to_string()))) + } } }; unsafe { raw::libssh2_sftp_seek64(self.raw, next) } - Ok(()) + Ok(next) } } @@ -547,3 +540,15 @@ impl FileStat { } } } + +#[cfg(unix)] +fn mkpath(v: Vec<u8>) -> PathBuf { + use std::os::unix::prelude::*; + use std::ffi::OsStr; + PathBuf::new(<OsStr as OsStrExt>::from_bytes(&v)) +} +#[cfg(windows)] +fn mkpath(v: Vec<u8>) -> PathBuf { + use std::str; + PathBuf::new(str::from_utf8(&v).unwrap()) +} diff --git a/src/util.rs b/src/util.rs index 11b7ffb..357cdce 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,6 @@ -use {Session, Error}; +use std::path::Path; + +use {raw, Session, Error}; #[doc(hidden)] pub trait Binding: Sized { @@ -24,3 +26,27 @@ pub trait SessionBinding<'sess>: Sized { } } } + +#[cfg(unix)] +pub fn path2bytes(p: &Path) -> Result<&[u8], Error> { + use std::os::unix::prelude::*; + use std::ffi::AsOsStr; + check(p.as_os_str().as_bytes()) +} +#[cfg(windows)] +pub fn path2bytes(p: &Path) -> Result<&[u8], Error> { + match p.to_str() { + Some(s) => check(s), + None => Error::new(raw::LIBSSH2_ERROR_INVAL, + "only unicode paths on windows may be used"), + } +} + +fn check(b: &[u8]) -> Result<&[u8], Error> { + if b.iter().any(|b| *b == 0) { + Err(Error::new(raw::LIBSSH2_ERROR_INVAL, + "path provided contains a 0 byte")) + } else { + Ok(b) + } +} diff --git a/tests/all.rs b/tests/all.rs index 491e4d4..a8d50af 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1,17 +1,18 @@ #![deny(warnings)] -#![feature(old_io, core, old_path, env)] +#![feature(io, core, path, env, net, fs)] extern crate ssh2; extern crate libc; use std::env; -use std::old_io::TcpStream; +use std::net::TcpStream; mod agent; mod session; mod channel; mod knownhosts; mod sftp; +mod tempdir; pub fn socket() -> TcpStream { TcpStream::connect("127.0.0.1:22").unwrap() diff --git a/tests/channel.rs b/tests/channel.rs index 254f0fe..38a39aa 100644 --- a/tests/channel.rs +++ b/tests/channel.rs @@ -1,4 +1,5 @@ -use std::old_io::{TcpListener, Listener, Acceptor, TcpStream}; +use std::io::prelude::*; +use std::net::{TcpStream, TcpListener}; use std::thread; #[test] @@ -20,8 +21,9 @@ fn reading_data() { let (_tcp, sess) = ::authed_session(); let mut channel = sess.channel_session().unwrap(); channel.exec("echo foo").unwrap(); - let output = channel.read_to_string().unwrap(); - assert_eq!(output.as_slice(), "foo\n"); + let mut output = String::new(); + channel.read_to_string(&mut output).unwrap(); + assert_eq!(output, "foo\n"); } #[test] @@ -31,8 +33,9 @@ fn writing_data() { channel.exec("read foo && echo $foo").unwrap(); channel.write_all(b"foo\n").unwrap(); channel.close().unwrap(); - let output = channel.read_to_string().unwrap(); - assert_eq!(output.as_slice(), "foo\n"); + let mut output = String::new(); + channel.read_to_string(&mut output).unwrap(); + assert_eq!(output, "foo\n"); } #[test] @@ -42,8 +45,9 @@ fn eof() { channel.adjust_receive_window(10, false).unwrap(); channel.exec("read foo").unwrap(); channel.send_eof().unwrap(); - let output = channel.read_to_string().unwrap(); - assert_eq!(output.as_slice(), ""); + let mut output = String::new(); + channel.read_to_string(&mut output).unwrap(); + assert_eq!(output, ""); } #[test] @@ -64,11 +68,10 @@ fn setenv() { #[test] fn direct() { - let mut l = TcpListener::bind("127.0.0.1:0").unwrap(); - let addr = l.socket_name().unwrap(); - let mut a = l.listen().unwrap(); + let a = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = a.socket_addr().unwrap(); let t = thread::scoped(move|| { - let mut s = a.accept().unwrap(); + let mut s = a.accept().unwrap().0; let b = &mut [0, 0, 0]; s.read(b).unwrap(); assert_eq!(b.as_slice(), [1, 2, 3].as_slice()); @@ -76,7 +79,7 @@ fn direct() { }); let (_tcp, sess) = ::authed_session(); let mut channel = sess.channel_direct_tcpip("127.0.0.1", - addr.port, None).unwrap(); + addr.port(), None).unwrap(); channel.write_all(&[1, 2, 3]).unwrap(); let r = &mut [0, 0, 0]; channel.read(r).unwrap(); @@ -90,7 +93,7 @@ fn forward() { let (mut listen, port) = sess.channel_forward_listen(39249, None, None) .unwrap(); let t = thread::scoped(move|| { - let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap(); + let mut s = TcpStream::connect(&("127.0.0.1", port)).unwrap(); let b = &mut [0, 0, 0]; s.read(b).unwrap(); assert_eq!(b.as_slice(), [1, 2, 3].as_slice()); diff --git a/tests/session.rs b/tests/session.rs index 8a97710..36f8c7c 100644 --- a/tests/session.rs +++ b/tests/session.rs @@ -1,5 +1,8 @@ use std::env; -use std::old_io::{self, File, TempDir}; +use std::fs::File; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; +use tempdir::TempDir; use ssh2::{Session, MethodType, HashType}; @@ -53,10 +56,12 @@ fn keepalive() { #[test] fn scp_recv() { let (_tcp, sess) = ::authed_session(); - let (mut ch, _) = sess.scp_recv(&Path::new(".ssh/authorized_keys")).unwrap(); - let data = ch.read_to_string().unwrap(); - let p = Path::new(env::var("HOME").unwrap()).join(".ssh/authorized_keys"); - let expected = File::open(&p).read_to_string().unwrap(); + let (mut ch, _) = sess.scp_recv(Path::new(".ssh/authorized_keys")).unwrap(); + let mut data = String::new(); + ch.read_to_string(&mut data).unwrap(); + let p = PathBuf::new(&env::var("HOME").unwrap()).join(".ssh/authorized_keys"); + let mut expected = String::new(); + File::open(&p).unwrap().read_to_string(&mut expected).unwrap(); assert!(data == expected); } @@ -64,10 +69,10 @@ fn scp_recv() { fn scp_send() { let td = TempDir::new("test").unwrap(); let (_tcp, sess) = ::authed_session(); - let mut ch = sess.scp_send(&td.path().join("foo"), - old_io::USER_FILE, 6, None).unwrap(); + let mut ch = sess.scp_send(&td.path().join("foo"), 0o644, 6, None).unwrap(); ch.write_all(b"foobar").unwrap(); drop(ch); - let actual = File::open(&td.path().join("foo")).read_to_end().unwrap(); - assert_eq!(actual.as_slice(), b"foobar"); + let mut actual = Vec::new(); + File::open(&td.path().join("foo")).unwrap().read_to_end(&mut actual).unwrap(); + assert_eq!(actual, b"foobar"); } diff --git a/tests/sftp.rs b/tests/sftp.rs index d3b8042..c0940a5 100644 --- a/tests/sftp.rs +++ b/tests/sftp.rs @@ -1,5 +1,7 @@ -use std::old_io::{self, fs, File, TempDir}; -use std::old_io::fs::PathExtensions; +use std::io::prelude::*; +use std::fs::{self, File}; + +use tempdir::TempDir; #[test] fn smoke() { @@ -11,22 +13,25 @@ fn smoke() { fn ops() { let td = TempDir::new("foo").unwrap(); File::create(&td.path().join("foo")).unwrap(); - fs::mkdir(&td.path().join("bar"), old_io::USER_DIR).unwrap(); + fs::create_dir(&td.path().join("bar")).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"), old_io::USER_DIR).unwrap(); + sftp.mkdir(&td.path().join("bar2"), 0o755).unwrap(); assert!(td.path().join("bar2").is_dir()); sftp.rmdir(&td.path().join("bar2")).unwrap(); sftp.create(&td.path().join("foo5")).unwrap().write_all(b"foo").unwrap(); - assert_eq!(File::open(&td.path().join("foo5")).read_to_end().unwrap(), - b"foo".to_vec()); + let mut v = Vec::new(); + File::open(&td.path().join("foo5")).unwrap().read_to_end(&mut v).unwrap(); + assert_eq!(v, b"foo"); assert_eq!(sftp.stat(&td.path().join("foo")).unwrap().size, Some(0)); - assert_eq!(foo.read_to_end().unwrap(), Vec::new()); + v.truncate(0); + foo.read_to_end(&mut v).unwrap(); + assert_eq!(v, Vec::new()); sftp.symlink(&td.path().join("foo"), &td.path().join("foo2")).unwrap(); diff --git a/tests/tempdir.rs b/tests/tempdir.rs new file mode 100644 index 0000000..cad52c6 --- /dev/null +++ b/tests/tempdir.rs @@ -0,0 +1,102 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +extern crate rand; + +use std::env; +use std::io::{self, Error, ErrorKind}; +use std::fs; +use std::path::{self, PathBuf, AsPath}; +use self::rand::{thread_rng, Rng}; + +/// A wrapper for a path to temporary directory implementing automatic +/// scope-based deletion. +pub struct TempDir { + path: Option<PathBuf>, +} + +// How many times should we (re)try finding an unused random name? It should be +// enough that an attacker will run out of luck before we run out of patience. +const NUM_RETRIES: u32 = 1 << 31; +// How many characters should we include in a random file name? It needs to +// be enough to dissuade an attacker from trying to preemptively create names +// of that length, but not so huge that we unnecessarily drain the random number +// generator of entropy. +const NUM_RAND_CHARS: usize = 12; + +impl TempDir { + /// Attempts to make a temporary directory inside of `tmpdir` whose name + /// will have the prefix `prefix`. The directory will be automatically + /// deleted once the returned wrapper is destroyed. + /// + /// If no directory can be created, `Err` is returned. + #[allow(deprecated)] // rand usage + pub fn new_in<P: AsPath + ?Sized>(tmpdir: &P, prefix: &str) + -> io::Result<TempDir> { + let tmpdir = tmpdir.as_path(); + assert!(tmpdir.is_absolute()); + + let mut rng = thread_rng(); + for _ in 0..NUM_RETRIES { + let suffix: String = rng.gen_ascii_chars().take(NUM_RAND_CHARS).collect(); + let leaf = if prefix.len() > 0 { + format!("{}.{}", prefix, suffix) + } else { + // If we're given an empty string for a prefix, then creating a + // directory starting with "." would lead to it being + // semi-invisible on some systems. + suffix + }; + let path = tmpdir.join(&leaf); + match fs::create_dir(&path) { + Ok(_) => return Ok(TempDir { path: Some(path) }), + Err(ref e) if e.kind() == ErrorKind::PathAlreadyExists => {} + Err(e) => return Err(e) + } + } + + Err(Error::new(ErrorKind::PathAlreadyExists, + "too many temporary directories already exist", + None)) + } + + /// Attempts to make a temporary directory inside of `env::temp_dir()` whose + /// name will have the prefix `prefix`. The directory will be automatically + /// deleted once the returned wrapper is destroyed. + /// + /// If no directory can be created, `Err` is returned. + #[allow(deprecated)] + pub fn new(prefix: &str) -> io::Result<TempDir> { + TempDir::new_in(&env::temp_dir(), prefix) + } + + /// Access the wrapped `std::path::Path` to the temporary directory. + pub fn path(&self) -> &path::Path { + self.path.as_ref().unwrap() + } + + fn cleanup_dir(&mut self) -> io::Result<()> { + match self.path { + Some(ref p) => fs::remove_dir_all(p), + None => Ok(()) + } + } +} + +impl Drop for TempDir { + fn drop(&mut self) { + let _ = self.cleanup_dir(); + } +} + +// the tests for this module need to change the path using change_dir, +// and this doesn't play nicely with other tests so these unit tests are located +// in src/test/run-pass/tempfile.rs + |