summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatteo Bigoi <bigo@crisidev.org>2020-11-22 17:16:40 +0000
committerGitHub <noreply@github.com>2020-11-22 17:16:40 +0000
commitc197df7df4045abdc2d4e8077781249dd83dbdd1 (patch)
treeacac6348029eef9c6fc67adf45cd87755117679a
parent1c5bc43ace2c32f37dfc6a308d8b6d687c2dc860 (diff)
downloadssh2-rs-c197df7df4045abdc2d4e8077781249dd83dbdd1.zip
Handle more precise SFTP error codes (#203)
* Handle more precise SFTP error codes * Allow set-env in macos github action * Bump to 0.9 since the Error interface has changed, hence this is a breaking change
-rw-r--r--.github/workflows/macos.yml2
-rw-r--r--.gitignore1
-rw-r--r--Cargo.toml4
-rw-r--r--libssh2-sys/lib.rs1
-rw-r--r--src/agent.rs11
-rw-r--r--src/error.rs206
-rw-r--r--src/knownhosts.rs4
-rw-r--r--src/lib.rs2
-rw-r--r--src/session.rs8
-rw-r--r--src/sftp.rs205
-rw-r--r--src/util.rs6
11 files changed, 281 insertions, 169 deletions
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 186b553..849ef70 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -24,6 +24,8 @@ jobs:
toolchain: stable
override: true
- name: Export OpenSSL environment variables
+ env:
+ ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
run: |
echo "::set-env name=OPENSSL_INCLUDE_DIR::$(brew --prefix openssl)/include"
echo "::set-env name=OPENSSL_LIB_DIR::$(brew --prefix openssl)/lib"
diff --git a/.gitignore b/.gitignore
index 5a07d6f..8d5918b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.*.sw*
/target/
+libssh2-sys/target/
/Cargo.lock
/tests/sshd
diff --git a/Cargo.toml b/Cargo.toml
index 7df7397..91e3240 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "ssh2"
-version = "0.8.3"
+version = "0.9.0"
authors = ["Alex Crichton <alex@alexcrichton.com>", "Wez Furlong <wez@wezfurlong.org>"]
license = "MIT/Apache-2.0"
keywords = ["ssh"]
@@ -19,7 +19,7 @@ vendored-openssl = ["libssh2-sys/vendored-openssl"]
[dependencies]
bitflags = "1.2"
libc = "0.2"
-libssh2-sys = { path = "libssh2-sys", version = "0.2.18" }
+libssh2-sys = { path = "libssh2-sys", version = "0.2.19" }
parking_lot = "0.10"
[dev-dependencies]
diff --git a/libssh2-sys/lib.rs b/libssh2-sys/lib.rs
index d1f6767..ad36642 100644
--- a/libssh2-sys/lib.rs
+++ b/libssh2-sys/lib.rs
@@ -350,6 +350,7 @@ extern "C" {
method_type: c_int,
algs: *mut *mut *const c_char,
) -> c_int;
+ pub fn libssh2_session_last_errno(sess: *mut LIBSSH2_SESSION) -> c_int;
pub fn libssh2_session_last_error(
sess: *mut LIBSSH2_SESSION,
msg: *mut *mut c_char,
diff --git a/src/agent.rs b/src/agent.rs
index 85ba4e6..0434002 100644
--- a/src/agent.rs
+++ b/src/agent.rs
@@ -4,7 +4,7 @@ use std::slice;
use std::str;
use std::sync::Arc;
-use {raw, Error, SessionInner};
+use {raw, Error, ErrorCode, SessionInner};
/// A structure representing a connection to an SSH agent.
///
@@ -110,9 +110,12 @@ impl Agent {
pub fn userauth(&self, username: &str, identity: &PublicKey) -> Result<(), Error> {
let username = CString::new(username)?;
let sess = self.sess.lock();
- let raw_ident = self
- .resolve_raw_identity(&sess, identity)?
- .ok_or_else(|| Error::new(raw::LIBSSH2_ERROR_BAD_USE, "Identity not found in agent"))?;
+ let raw_ident = self.resolve_raw_identity(&sess, identity)?.ok_or_else(|| {
+ Error::new(
+ ErrorCode::Session(raw::LIBSSH2_ERROR_BAD_USE),
+ "Identity not found in agent",
+ )
+ })?;
unsafe {
sess.rc(raw::libssh2_agent_userauth(
self.raw,
diff --git a/src/error.rs b/src/error.rs
index be12eed..e3a3c56 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -9,17 +9,38 @@ use std::str;
use {raw, Session};
+/// An error code originating from a particular source.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum ErrorCode {
+ /// Codes for errors that originate in libssh2.
+ /// Can be one of `LIBSSH2_ERROR_*` constants.
+ Session(libc::c_int),
+
+ /// Codes for errors that originate in the SFTP subsystem.
+ /// Can be one of `LIBSSH2_FX_*` constants.
+ //
+ // TODO: This should be `c_ulong` instead of `c_int` because these constants
+ // are only returned by `libssh2_sftp_last_error()` which returns `c_ulong`.
+ SFTP(libc::c_int),
+}
+
+impl fmt::Display for ErrorCode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
/// Representation of an error that can occur within libssh2
#[derive(Debug)]
#[allow(missing_copy_implementations)]
pub struct Error {
- code: libc::c_int,
+ code: ErrorCode,
msg: Cow<'static, str>,
}
impl Error {
#[doc(hidden)]
- pub fn last_error_raw(raw: *mut raw::LIBSSH2_SESSION) -> Option<Error> {
+ pub fn last_session_error_raw(raw: *mut raw::LIBSSH2_SESSION) -> Option<Error> {
unsafe {
let mut msg = null_mut();
let rc = raw::libssh2_session_last_error(raw, &mut msg, null_mut(), 0);
@@ -31,7 +52,7 @@ impl Error {
// LIBSSH2_SESSION, so the error message should be copied before
// it is overwritten by the next API call.
Some(Self {
- code: rc,
+ code: ErrorCode::Session(rc),
msg: make_error_message(msg),
})
}
@@ -53,14 +74,14 @@ impl Error {
let mut msg = null_mut();
let res = raw::libssh2_session_last_error(raw, &mut msg, null_mut(), 0);
if res != rc {
- return Self::from_errno(rc);
+ return Self::from_errno(ErrorCode::Session(rc));
}
// The pointer stored in `msg` points to the internal buffer of
// LIBSSH2_SESSION, so the error message should be copied before
// it is overwritten by the next API call.
Self {
- code: rc,
+ code: ErrorCode::Session(rc),
msg: make_error_message(msg),
}
}
@@ -69,106 +90,109 @@ impl Error {
/// Generate the last error that occurred for a `Session`.
///
/// Returns `None` if there was no last error.
- pub fn last_error(sess: &Session) -> Option<Error> {
- Self::last_error_raw(&mut *sess.raw())
+ pub fn last_session_error(sess: &Session) -> Option<Error> {
+ Self::last_session_error_raw(&mut *sess.raw())
}
/// Create a new error for the given code and message
- pub fn new(code: libc::c_int, msg: &'static str) -> Error {
+ pub fn new(code: ErrorCode, msg: &'static str) -> Error {
Error {
- code: code,
+ code,
msg: Cow::Borrowed(msg),
}
}
/// Generate an error that represents EOF
pub fn eof() -> Error {
- Error::new(raw::LIBSSH2_ERROR_CHANNEL_EOF_SENT, "end of file")
+ Error::new(
+ ErrorCode::Session(raw::LIBSSH2_ERROR_CHANNEL_EOF_SENT),
+ "end of file",
+ )
}
/// Generate an error for unknown failure
pub fn unknown() -> Error {
- Error::new(libc::c_int::min_value(), "no other error listed")
- }
-
- pub(crate) fn rc(rc: libc::c_int) -> Result<(), Error> {
- if rc == 0 {
- Ok(())
- } else {
- Err(Self::from_errno(rc))
- }
+ Error::new(
+ ErrorCode::Session(libc::c_int::min_value()),
+ "no other error listed",
+ )
}
/// Construct an error from an error code from libssh2
- pub fn from_errno(code: libc::c_int) -> Error {
+ pub fn from_errno(code: ErrorCode) -> Error {
let msg = match code {
- raw::LIBSSH2_ERROR_BANNER_RECV => "banner recv failure",
- raw::LIBSSH2_ERROR_BANNER_SEND => "banner send failure",
- raw::LIBSSH2_ERROR_INVALID_MAC => "invalid mac",
- raw::LIBSSH2_ERROR_KEX_FAILURE => "kex failure",
- raw::LIBSSH2_ERROR_ALLOC => "alloc failure",
- raw::LIBSSH2_ERROR_SOCKET_SEND => "socket send faiulre",
- raw::LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE => "key exchange failure",
- raw::LIBSSH2_ERROR_TIMEOUT => "timed out",
- raw::LIBSSH2_ERROR_HOSTKEY_INIT => "hostkey init error",
- raw::LIBSSH2_ERROR_HOSTKEY_SIGN => "hostkey sign error",
- raw::LIBSSH2_ERROR_DECRYPT => "decrypt error",
- raw::LIBSSH2_ERROR_SOCKET_DISCONNECT => "socket disconnected",
- raw::LIBSSH2_ERROR_PROTO => "protocol error",
- raw::LIBSSH2_ERROR_PASSWORD_EXPIRED => "password expired",
- raw::LIBSSH2_ERROR_FILE => "file error",
- raw::LIBSSH2_ERROR_METHOD_NONE => "bad method name",
- raw::LIBSSH2_ERROR_AUTHENTICATION_FAILED => "authentication failed",
- raw::LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED => "public key unverified",
- raw::LIBSSH2_ERROR_CHANNEL_OUTOFORDER => "channel out of order",
- raw::LIBSSH2_ERROR_CHANNEL_FAILURE => "channel failure",
- raw::LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED => "request denied",
- raw::LIBSSH2_ERROR_CHANNEL_UNKNOWN => "unknown channel error",
- raw::LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED => "window exceeded",
- raw::LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED => "packet exceeded",
- raw::LIBSSH2_ERROR_CHANNEL_CLOSED => "closed channel",
- raw::LIBSSH2_ERROR_CHANNEL_EOF_SENT => "eof sent",
- raw::LIBSSH2_ERROR_SCP_PROTOCOL => "scp protocol error",
- raw::LIBSSH2_ERROR_ZLIB => "zlib error",
- raw::LIBSSH2_ERROR_SOCKET_TIMEOUT => "socket timeout",
- raw::LIBSSH2_ERROR_SFTP_PROTOCOL => "sftp protocol error",
- raw::LIBSSH2_ERROR_REQUEST_DENIED => "request denied",
- raw::LIBSSH2_ERROR_METHOD_NOT_SUPPORTED => "method not supported",
- raw::LIBSSH2_ERROR_INVAL => "invalid",
- raw::LIBSSH2_ERROR_INVALID_POLL_TYPE => "invalid poll type",
- raw::LIBSSH2_ERROR_PUBLICKEY_PROTOCOL => "public key protocol error",
- raw::LIBSSH2_ERROR_EAGAIN => "operation would block",
- raw::LIBSSH2_ERROR_BUFFER_TOO_SMALL => "buffer too small",
- raw::LIBSSH2_ERROR_BAD_USE => "bad use error",
- raw::LIBSSH2_ERROR_COMPRESS => "compression error",
- raw::LIBSSH2_ERROR_OUT_OF_BOUNDARY => "out of bounds",
- raw::LIBSSH2_ERROR_AGENT_PROTOCOL => "invalid agent protocol",
- raw::LIBSSH2_ERROR_SOCKET_RECV => "error receiving on socket",
- raw::LIBSSH2_ERROR_ENCRYPT => "bad encrypt",
- raw::LIBSSH2_ERROR_BAD_SOCKET => "bad socket",
- raw::LIBSSH2_ERROR_KNOWN_HOSTS => "known hosts error",
- raw::LIBSSH2_FX_EOF => "end of file",
- raw::LIBSSH2_FX_NO_SUCH_FILE => "no such file",
- raw::LIBSSH2_FX_PERMISSION_DENIED => "permission denied",
- raw::LIBSSH2_FX_FAILURE => "failure",
- raw::LIBSSH2_FX_BAD_MESSAGE => "bad message",
- raw::LIBSSH2_FX_NO_CONNECTION => "no connection",
- raw::LIBSSH2_FX_CONNECTION_LOST => "connection lost",
- raw::LIBSSH2_FX_OP_UNSUPPORTED => "operation unsupported",
- raw::LIBSSH2_FX_INVALID_HANDLE => "invalid handle",
- raw::LIBSSH2_FX_NO_SUCH_PATH => "no such path",
- raw::LIBSSH2_FX_FILE_ALREADY_EXISTS => "file already exists",
- raw::LIBSSH2_FX_WRITE_PROTECT => "file is write protected",
- raw::LIBSSH2_FX_NO_MEDIA => "no media available",
- raw::LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM => "no space on filesystem",
- raw::LIBSSH2_FX_QUOTA_EXCEEDED => "quota exceeded",
- raw::LIBSSH2_FX_UNKNOWN_PRINCIPAL => "unknown principal",
- raw::LIBSSH2_FX_LOCK_CONFLICT => "lock conflict",
- raw::LIBSSH2_FX_DIR_NOT_EMPTY => "directory not empty",
- raw::LIBSSH2_FX_NOT_A_DIRECTORY => "not a directory",
- raw::LIBSSH2_FX_INVALID_FILENAME => "invalid filename",
- raw::LIBSSH2_FX_LINK_LOOP => "link loop",
- _ => "unknown error",
+ ErrorCode::Session(code) => match code {
+ raw::LIBSSH2_ERROR_BANNER_RECV => "banner recv failure",
+ raw::LIBSSH2_ERROR_BANNER_SEND => "banner send failure",
+ raw::LIBSSH2_ERROR_INVALID_MAC => "invalid mac",
+ raw::LIBSSH2_ERROR_KEX_FAILURE => "kex failure",
+ raw::LIBSSH2_ERROR_ALLOC => "alloc failure",
+ raw::LIBSSH2_ERROR_SOCKET_SEND => "socket send faiulre",
+ raw::LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE => "key exchange failure",
+ raw::LIBSSH2_ERROR_TIMEOUT => "timed out",
+ raw::LIBSSH2_ERROR_HOSTKEY_INIT => "hostkey init error",
+ raw::LIBSSH2_ERROR_HOSTKEY_SIGN => "hostkey sign error",
+ raw::LIBSSH2_ERROR_DECRYPT => "decrypt error",
+ raw::LIBSSH2_ERROR_SOCKET_DISCONNECT => "socket disconnected",
+ raw::LIBSSH2_ERROR_PROTO => "protocol error",
+ raw::LIBSSH2_ERROR_PASSWORD_EXPIRED => "password expired",
+ raw::LIBSSH2_ERROR_FILE => "file error",
+ raw::LIBSSH2_ERROR_METHOD_NONE => "bad method name",
+ raw::LIBSSH2_ERROR_AUTHENTICATION_FAILED => "authentication failed",
+ raw::LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED => "public key unverified",
+ raw::LIBSSH2_ERROR_CHANNEL_OUTOFORDER => "channel out of order",
+ raw::LIBSSH2_ERROR_CHANNEL_FAILURE => "channel failure",
+ raw::LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED => "request denied",
+ raw::LIBSSH2_ERROR_CHANNEL_UNKNOWN => "unknown channel error",
+ raw::LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED => "window exceeded",
+ raw::LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED => "packet exceeded",
+ raw::LIBSSH2_ERROR_CHANNEL_CLOSED => "closed channel",
+ raw::LIBSSH2_ERROR_CHANNEL_EOF_SENT => "eof sent",
+ raw::LIBSSH2_ERROR_SCP_PROTOCOL => "scp protocol error",
+ raw::LIBSSH2_ERROR_ZLIB => "zlib error",
+ raw::LIBSSH2_ERROR_SOCKET_TIMEOUT => "socket timeout",
+ raw::LIBSSH2_ERROR_SFTP_PROTOCOL => "sftp protocol error",
+ raw::LIBSSH2_ERROR_REQUEST_DENIED => "request denied",
+ raw::LIBSSH2_ERROR_METHOD_NOT_SUPPORTED => "method not supported",
+ raw::LIBSSH2_ERROR_INVAL => "invalid",
+ raw::LIBSSH2_ERROR_INVALID_POLL_TYPE => "invalid poll type",
+ raw::LIBSSH2_ERROR_PUBLICKEY_PROTOCOL => "public key protocol error",
+ raw::LIBSSH2_ERROR_EAGAIN => "operation would block",
+ raw::LIBSSH2_ERROR_BUFFER_TOO_SMALL => "buffer too small",
+ raw::LIBSSH2_ERROR_BAD_USE => "bad use error",
+ raw::LIBSSH2_ERROR_COMPRESS => "compression error",
+ raw::LIBSSH2_ERROR_OUT_OF_BOUNDARY => "out of bounds",
+ raw::LIBSSH2_ERROR_AGENT_PROTOCOL => "invalid agent protocol",
+ raw::LIBSSH2_ERROR_SOCKET_RECV => "error receiving on socket",
+ raw::LIBSSH2_ERROR_ENCRYPT => "bad encrypt",
+ raw::LIBSSH2_ERROR_BAD_SOCKET => "bad socket",
+ raw::LIBSSH2_ERROR_KNOWN_HOSTS => "known hosts error",
+ _ => "unknown error",
+ },
+ ErrorCode::SFTP(code) => match code {
+ raw::LIBSSH2_FX_EOF => "end of file",
+ raw::LIBSSH2_FX_NO_SUCH_FILE => "no such file",
+ raw::LIBSSH2_FX_PERMISSION_DENIED => "permission denied",
+ raw::LIBSSH2_FX_FAILURE => "failure",
+ raw::LIBSSH2_FX_BAD_MESSAGE => "bad message",
+ raw::LIBSSH2_FX_NO_CONNECTION => "no connection",
+ raw::LIBSSH2_FX_CONNECTION_LOST => "connection lost",
+ raw::LIBSSH2_FX_OP_UNSUPPORTED => "operation unsupported",
+ raw::LIBSSH2_FX_INVALID_HANDLE => "invalid handle",
+ raw::LIBSSH2_FX_NO_SUCH_PATH => "no such path",
+ raw::LIBSSH2_FX_FILE_ALREADY_EXISTS => "file already exists",
+ raw::LIBSSH2_FX_WRITE_PROTECT => "file is write protected",
+ raw::LIBSSH2_FX_NO_MEDIA => "no media available",
+ raw::LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM => "no space on filesystem",
+ raw::LIBSSH2_FX_QUOTA_EXCEEDED => "quota exceeded",
+ raw::LIBSSH2_FX_UNKNOWN_PRINCIPAL => "unknown principal",
+ raw::LIBSSH2_FX_LOCK_CONFLICT => "lock conflict",
+ raw::LIBSSH2_FX_DIR_NOT_EMPTY => "directory not empty",
+ raw::LIBSSH2_FX_NOT_A_DIRECTORY => "not a directory",
+ raw::LIBSSH2_FX_INVALID_FILENAME => "invalid filename",
+ raw::LIBSSH2_FX_LINK_LOOP => "link loop",
+ _ => "unknown error",
+ },
};
Error::new(code, msg)
}
@@ -179,7 +203,7 @@ impl Error {
}
/// Return the code for this error
- pub fn code(&self) -> libc::c_int {
+ pub fn code(&self) -> ErrorCode {
self.code
}
}
@@ -187,8 +211,8 @@ impl Error {
impl From<Error> for io::Error {
fn from(err: Error) -> io::Error {
let kind = match err.code {
- raw::LIBSSH2_ERROR_EAGAIN => io::ErrorKind::WouldBlock,
- raw::LIBSSH2_ERROR_TIMEOUT => io::ErrorKind::TimedOut,
+ ErrorCode::Session(raw::LIBSSH2_ERROR_EAGAIN) => io::ErrorKind::WouldBlock,
+ ErrorCode::Session(raw::LIBSSH2_ERROR_TIMEOUT) => io::ErrorKind::TimedOut,
_ => io::ErrorKind::Other,
};
io::Error::new(kind, err.msg)
@@ -210,7 +234,7 @@ impl error::Error for Error {
impl From<NulError> for Error {
fn from(_: NulError) -> Error {
Error::new(
- raw::LIBSSH2_ERROR_INVAL,
+ ErrorCode::Session(raw::LIBSSH2_ERROR_INVAL),
"provided data contained a nul byte and could not be used \
as as string",
)
diff --git a/src/knownhosts.rs b/src/knownhosts.rs
index 2750fdd..25a623b 100644
--- a/src/knownhosts.rs
+++ b/src/knownhosts.rs
@@ -6,7 +6,7 @@ use std::str;
use std::sync::Arc;
use util;
-use {raw, CheckResult, Error, KnownHostFileKind, SessionInner};
+use {raw, CheckResult, Error, ErrorCode, KnownHostFileKind, SessionInner};
/// A set of known hosts which can be used to verify the identity of a remote
/// server.
@@ -113,7 +113,7 @@ impl KnownHosts {
let sess = self.sess.lock();
let raw_host = self.resolve_to_raw_host(&sess, host)?.ok_or_else(|| {
Error::new(
- raw::LIBSSH2_ERROR_BAD_USE,
+ ErrorCode::Session(raw::LIBSSH2_ERROR_BAD_USE),
"Host is not in the set of known hosts",
)
})?;
diff --git a/src/lib.rs b/src/lib.rs
index 823c24c..47467c7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -229,7 +229,7 @@ use std::ffi::CStr;
pub use agent::{Agent, PublicKey};
pub use channel::{Channel, ExitSignal, ReadWindow, Stream, WriteWindow};
-pub use error::Error;
+pub use error::{Error, ErrorCode};
pub use knownhosts::{Host, KnownHosts};
pub use listener::Listener;
use session::SessionInner;
diff --git a/src/session.rs b/src/session.rs
index 25aebc9..33bb619 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -15,7 +15,7 @@ use std::str;
use std::sync::Arc;
use util;
-use {raw, ByApplication, DisconnectCode, Error, HostKeyType};
+use {raw, ByApplication, DisconnectCode, Error, ErrorCode, HostKeyType};
use {Agent, Channel, HashType, KnownHosts, Listener, MethodType, Sftp};
/// Called by libssh2 to respond to some number of challenges as part of
@@ -265,7 +265,7 @@ impl Session {
unsafe {
let stream = inner.tcp.as_ref().ok_or_else(|| {
Error::new(
- raw::LIBSSH2_ERROR_BAD_SOCKET,
+ ErrorCode::Session(raw::LIBSSH2_ERROR_BAD_SOCKET),
"use set_tcp_stream() to associate with a TcpStream",
)
})?;
@@ -447,7 +447,7 @@ impl Session {
Some(identity) => identity,
None => {
return Err(Error::new(
- raw::LIBSSH2_ERROR_INVAL as c_int,
+ ErrorCode::Session(raw::LIBSSH2_ERROR_INVAL),
"no identities found in the ssh agent",
))
}
@@ -1037,7 +1037,7 @@ impl SessionInner {
}
pub fn last_error(&self) -> Option<Error> {
- Error::last_error_raw(self.raw)
+ Error::last_session_error_raw(self.raw)
}
/// Set or clear blocking mode on session
diff --git a/src/sftp.rs b/src/sftp.rs
index 2ef2af8..323bc6f 100644
--- a/src/sftp.rs
+++ b/src/sftp.rs
@@ -1,5 +1,6 @@
use libc::{c_int, c_long, c_uint, c_ulong, size_t};
use parking_lot::{Mutex, MutexGuard};
+use std::convert::TryFrom;
use std::io::prelude::*;
use std::io::{self, ErrorKind, SeekFrom};
use std::mem;
@@ -7,7 +8,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use util;
-use {raw, Error, SessionInner};
+use {raw, Error, ErrorCode, SessionInner};
/// A handle to a remote filesystem over SFTP.
///
@@ -173,7 +174,8 @@ impl Sftp {
open_type as c_int,
);
if ret.is_null() {
- Err(locked.sess.last_error().unwrap_or_else(Error::unknown))
+ let rc = raw::libssh2_session_last_errno(locked.sess.raw);
+ Err(Self::error_code_into_error(locked.sess.raw, locked.raw, rc))
} else {
Ok(File::from_raw(self, ret))
}
@@ -216,7 +218,7 @@ impl Sftp {
ret.push((dirname.join(&filename), stat))
}
- Err(ref e) if e.code() == raw::LIBSSH2_ERROR_FILE => break,
+ Err(ref e) if e.code() == ErrorCode::Session(raw::LIBSSH2_ERROR_FILE) => break,
Err(e) => return Err(e),
}
}
@@ -227,7 +229,7 @@ impl Sftp {
pub fn mkdir(&self, filename: &Path, mode: i32) -> Result<(), Error> {
let filename = util::path2bytes(filename)?;
let locked = self.lock()?;
- locked.sess.rc(unsafe {
+ Self::rc(&locked, unsafe {
raw::libssh2_sftp_mkdir_ex(
locked.raw,
filename.as_ptr() as *const _,
@@ -256,15 +258,17 @@ impl Sftp {
let locked = self.lock()?;
unsafe {
let mut ret = mem::zeroed();
- let rc = raw::libssh2_sftp_stat_ex(
- locked.raw,
- filename.as_ptr() as *const _,
- filename.len() as c_uint,
- raw::LIBSSH2_SFTP_STAT,
- &mut ret,
- );
- locked.sess.rc(rc)?;
- Ok(FileStat::from_raw(&ret))
+ Self::rc(
+ &locked,
+ raw::libssh2_sftp_stat_ex(
+ locked.raw,
+ filename.as_ptr() as *const _,
+ filename.len() as c_uint,
+ raw::LIBSSH2_SFTP_STAT,
+ &mut ret,
+ ),
+ )
+ .map(|_| FileStat::from_raw(&ret))
}
}
@@ -274,15 +278,17 @@ impl Sftp {
let locked = self.lock()?;
unsafe {
let mut ret = mem::zeroed();
- let rc = raw::libssh2_sftp_stat_ex(
- locked.raw,
- filename.as_ptr() as *const _,
- filename.len() as c_uint,
- raw::LIBSSH2_SFTP_LSTAT,
- &mut ret,
- );
- locked.sess.rc(rc)?;
- Ok(FileStat::from_raw(&ret))
+ Self::rc(
+ &locked,
+ raw::libssh2_sftp_stat_ex(
+ locked.raw,
+ filename.as_ptr() as *const _,
+ filename.len() as c_uint,
+ raw::LIBSSH2_SFTP_LSTAT,
+ &mut ret,
+ ),
+ )
+ .map(|_| FileStat::from_raw(&ret))
}
}
@@ -290,7 +296,7 @@ impl Sftp {
pub fn setstat(&self, filename: &Path, stat: FileStat) -> Result<(), Error> {
let filename = util::path2bytes(filename)?;
let locked = self.lock()?;
- locked.sess.rc(unsafe {
+ Self::rc(&locked, unsafe {
let mut raw = stat.raw();
raw::libssh2_sftp_stat_ex(
locked.raw,
@@ -352,12 +358,10 @@ impl Sftp {
break;
}
}
- if rc < 0 {
- Err(Error::from_session_error_raw(locked.sess.raw, rc))
- } else {
+ Self::rc(&locked, rc).map(move |_| {
unsafe { ret.set_len(rc as usize) }
- Ok(mkpath(ret))
- }
+ mkpath(ret)
+ })
}
/// Rename a filesystem object on the remote filesystem.
@@ -378,7 +382,7 @@ impl Sftp {
let src = util::path2bytes(src)?;
let dst = util::path2bytes(dst)?;
let locked = self.lock()?;
- locked.sess.rc(unsafe {
+ Self::rc(&locked, unsafe {
raw::libssh2_sftp_rename_ex(
locked.raw,
src.as_ptr() as *const _,
@@ -394,7 +398,7 @@ impl Sftp {
pub fn unlink(&self, file: &Path) -> Result<(), Error> {
let file = util::path2bytes(file)?;
let locked = self.lock()?;
- locked.sess.rc(unsafe {
+ Self::rc(&locked, unsafe {
raw::libssh2_sftp_unlink_ex(locked.raw, file.as_ptr() as *const _, file.len() as c_uint)
})
}
@@ -412,7 +416,9 @@ impl Sftp {
raw: sftp_inner.raw,
})
}
- None => Err(Error::from_errno(raw::LIBSSH2_ERROR_BAD_USE)),
+ None => Err(Error::from_errno(ErrorCode::Session(
+ raw::LIBSSH2_ERROR_BAD_USE,
+ ))),
}
}
@@ -440,16 +446,58 @@ impl Sftp {
Err(sftp_inner_arc) => {
// We are failing shutdown as there are files left open, keep this object usable
self.inner = Some(sftp_inner_arc);
- Err(Error::from_errno(raw::LIBSSH2_ERROR_BAD_USE))
+ Err(Error::from_errno(ErrorCode::Session(
+ raw::LIBSSH2_ERROR_BAD_USE,
+ )))
}
}
}
None => {
// We have already shut this down. Shutting down twice is a mistake from the caller code
- Err(Error::from_errno(raw::LIBSSH2_ERROR_BAD_USE))
+ Err(Error::from_errno(ErrorCode::Session(
+ raw::LIBSSH2_ERROR_BAD_USE,
+ )))
+ }
+ }
+ }
+
+ fn error_code_into_error(
+ session_raw: *mut raw::LIBSSH2_SESSION,
+ sftp_raw: *mut raw::LIBSSH2_SFTP,
+ rc: libc::c_int,
+ ) -> Error {
+ if rc >= 0 {
+ Error::unknown()
+ } else if rc == raw::LIBSSH2_ERROR_SFTP_PROTOCOL {
+ let actual_rc = unsafe { raw::libssh2_sftp_last_error(sftp_raw) };
+ // TODO: This conversion from `c_ulong` to `c_int` should not be
+ // necessary if the constants `LIBSSH2_FX_*` in the `-sys` crate
+ // are typed as `c_ulong`, as they should be.
+ if let Ok(actual_rc) = libc::c_int::try_from(actual_rc) {
+ Error::from_errno(ErrorCode::SFTP(actual_rc))
+ } else {
+ Error::unknown()
}
+ } else {
+ Error::from_session_error_raw(session_raw, rc)
+ }
+ }
+
+ fn error_code_into_result(
+ session_raw: *mut raw::LIBSSH2_SESSION,
+ sftp_raw: *mut raw::LIBSSH2_SFTP,
+ rc: libc::c_int,
+ ) -> Result<(), Error> {
+ if rc >= 0 {
+ Ok(())
+ } else {
+ Err(Self::error_code_into_error(session_raw, sftp_raw, rc))
}
}
+
+ fn rc(locked: &LockedSftp, rc: libc::c_int) -> Result<(), Error> {
+ Self::error_code_into_result(locked.sess.raw, locked.raw, rc)
+ }
}
impl Drop for SftpInnerDropWrapper {
@@ -490,7 +538,7 @@ impl File {
/// Set the metadata for this handle.
pub fn setstat(&mut self, stat: FileStat) -> Result<(), Error> {
let locked = self.lock()?;
- locked.sess.rc(unsafe {
+ self.rc(&locked, unsafe {
let mut raw = stat.raw();
raw::libssh2_sftp_fstat_ex(locked.raw, &mut raw, 1)
})
@@ -501,10 +549,8 @@ impl File {
let locked = self.lock()?;
unsafe {
let mut ret = mem::zeroed();
- locked
- .sess
- .rc(raw::libssh2_sftp_fstat_ex(locked.raw, &mut ret, 0))?;
- Ok(FileStat::from_raw(&ret))
+ self.rc(&locked, raw::libssh2_sftp_fstat_ex(locked.raw, &mut ret, 0))
+ .map(|_| FileStat::from_raw(&ret))
}
}
@@ -513,10 +559,8 @@ impl File {
let locked = self.lock()?;
unsafe {
let mut ret = mem::zeroed();
- locked
- .sess
- .rc(raw::libssh2_sftp_fstatvfs(locked.raw, &mut ret))?;
- Ok(ret)
+ self.rc(&locked, raw::libssh2_sftp_fstatvfs(locked.raw, &mut ret))
+ .map(move |_| ret)
}
}
@@ -554,16 +598,19 @@ impl File {
break;
}
}
- if rc < 0 {
- return Err(Error::from_session_error_raw(locked.sess.raw, rc));
- } else if rc == 0 {
- return Err(Error::new(raw::LIBSSH2_ERROR_FILE, "no more files"));
+ if rc == 0 {
+ Err(Error::new(
+ ErrorCode::Session(raw::LIBSSH2_ERROR_FILE),
+ "no more files",
+ ))
} else {
- unsafe {
- buf.set_len(rc as usize);
- }
+ self.rc(&locked, rc).map(move |_| {
+ unsafe {
+ buf.set_len(rc as usize);
+ }
+ (mkpath(buf), FileStat::from_raw(&stat))
+ })
}
- Ok((mkpath(buf), FileStat::from_raw(&stat)))
}
/// This function causes the remote server to synchronize the file data and
@@ -572,9 +619,7 @@ impl File {
/// For this to work requires fsync@openssh.com support on the server.
pub fn fsync(&mut self) -> Result<(), Error> {
let locked = self.lock()?;
- locked
- .sess
- .rc(unsafe { raw::libssh2_sftp_fsync(locked.raw) })
+ self.rc(&locked, unsafe { raw::libssh2_sftp_fsync(locked.raw) })
}
fn lock(&self) -> Result<LockedFile, Error> {
@@ -590,7 +635,9 @@ impl File {
raw: file_inner.raw,
})
}
- None => Err(Error::from_errno(raw::LIBSSH2_ERROR_BAD_USE)),
+ None => Err(Error::from_errno(ErrorCode::Session(
+ raw::LIBSSH2_ERROR_BAD_USE,
+ ))),
}
}
@@ -598,24 +645,48 @@ impl File {
pub fn close(&mut self) -> Result<(), Error> {
{
let locked = self.lock()?;
- Error::rc(unsafe { raw::libssh2_sftp_close_handle(locked.raw) })?;
+ self.rc(&locked, unsafe {
+ raw::libssh2_sftp_close_handle(locked.raw)
+ })?;
}
self.inner = None;
Ok(())
}
+
+ fn rc(&self, locked: &LockedFile, rc: libc::c_int) -> Result<(), Error> {
+ if let Some(file_inner) = self.inner.as_ref() {
+ let sftp_inner = file_inner.sftp.0.as_ref().expect(
+ "We are holding an Arc<SftpInnerDropWrapper>, \
+ so nobody could unset this (set on creation)",
+ );
+ Sftp::error_code_into_result(locked.sess.raw, sftp_inner.raw, rc)
+ } else if rc < 0 {
+ Err(Error::from_errno(ErrorCode::Session(rc)))
+ } else {
+ Ok(())
+ }
+ }
}
impl Read for File {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let locked = self.lock()?;
- unsafe {
- let rc =
- raw::libssh2_sftp_read(locked.raw, buf.as_mut_ptr() as *mut _, buf.len() as size_t);
- if rc < 0 {
- Err(Error::from_session_error_raw(locked.sess.raw, rc as _).into())
+ let rc = unsafe {
+ raw::libssh2_sftp_read(locked.raw, buf.as_mut_ptr() as *mut _, buf.len() as size_t)
+ };
+ if rc < 0 {
+ let rc = rc as libc::c_int;
+ if let Some(file_inner) = self.inner.as_ref() {
+ let sftp_inner = file_inner.sftp.0.as_ref().expect(
+ "We are holding an Arc<SftpInnerDropWrapper>, \
+ so nobody could unset this (set on creation)",
+ );
+ Err(Sftp::error_code_into_error(locked.sess.raw, sftp_inner.raw, rc).into())
} else {
- Ok(rc as usize)
+ Err(Error::from_errno(ErrorCode::Session(rc)).into())
}
+ } else {
+ Ok(rc as usize)
}
}
}
@@ -627,11 +698,21 @@ impl Write for File {
raw::libssh2_sftp_write(locked.raw, buf.as_ptr() as *const _, buf.len() as size_t)
};
if rc < 0 {
- Err(Error::from_session_error_raw(locked.sess.raw, rc as _).into())
+ let rc = rc as libc::c_int;
+ if let Some(file_inner) = self.inner.as_ref() {
+ let sftp_inner = file_inner.sftp.0.as_ref().expect(
+ "We are holding an Arc<SftpInnerDropWrapper>, \
+ so nobody could unset this (set on creation)",
+ );
+ Err(Sftp::error_code_into_error(locked.sess.raw, sftp_inner.raw, rc).into())
+ } else {
+ Err(Error::from_errno(ErrorCode::Session(rc)).into())
+ }
} else {
Ok(rc as usize)
}
}
+
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
diff --git a/src/util.rs b/src/util.rs
index 96cb1c5..258aa59 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,7 +1,7 @@
use std::borrow::Cow;
use std::path::Path;
-use {raw, Error};
+use {raw, Error, ErrorCode};
#[cfg(unix)]
pub fn path2bytes(p: &Path) -> Result<Cow<[u8]>, Error> {
@@ -16,7 +16,7 @@ pub fn path2bytes(p: &Path) -> Result<Cow<[u8]>, Error> {
.map(|s| s.as_bytes())
.ok_or_else(|| {
Error::new(
- raw::LIBSSH2_ERROR_INVAL,
+ ErrorCode::Session(raw::LIBSSH2_ERROR_INVAL),
"only unicode paths on windows may be used",
)
})
@@ -40,7 +40,7 @@ pub fn path2bytes(p: &Path) -> Result<Cow<[u8]>, Error> {
fn check(b: Cow<[u8]>) -> Result<Cow<[u8]>, Error> {
if b.iter().any(|b| *b == 0) {
Err(Error::new(
- raw::LIBSSH2_ERROR_INVAL,
+ ErrorCode::Session(raw::LIBSSH2_ERROR_INVAL),
"path provided contains a 0 byte",
))
} else {