summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml5
-rw-r--r--libssh2-sys/Cargo.toml4
-rw-r--r--libssh2-sys/build.rs61
-rw-r--r--src/channel.rs52
-rw-r--r--src/knownhosts.rs13
-rw-r--r--src/lib.rs36
-rw-r--r--src/session.rs140
-rw-r--r--src/sftp.rs157
-rw-r--r--src/util.rs28
-rw-r--r--tests/all.rs5
-rw-r--r--tests/channel.rs29
-rw-r--r--tests/session.rs23
-rw-r--r--tests/sftp.rs19
-rw-r--r--tests/tempdir.rs102
14 files changed, 420 insertions, 254 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 99a6925..a5bbb3f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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)
diff --git a/src/lib.rs b/src/lib.rs
index d53c641..640a640 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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
+