From c197df7df4045abdc2d4e8077781249dd83dbdd1 Mon Sep 17 00:00:00 2001 From: Matteo Bigoi Date: Sun, 22 Nov 2020 17:16:40 +0000 Subject: 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 --- .github/workflows/macos.yml | 2 + .gitignore | 1 + Cargo.toml | 4 +- libssh2-sys/lib.rs | 1 + src/agent.rs | 11 ++- src/error.rs | 206 +++++++++++++++++++++++++------------------- src/knownhosts.rs | 4 +- src/lib.rs | 2 +- src/session.rs | 8 +- src/sftp.rs | 205 ++++++++++++++++++++++++++++++------------- src/util.rs | 6 +- 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 ", "Wez Furlong "] 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 { + pub fn last_session_error_raw(raw: *mut raw::LIBSSH2_SESSION) -> Option { 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 { - Self::last_error_raw(&mut *sess.raw()) + pub fn last_session_error(sess: &Session) -> Option { + 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 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 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::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 { @@ -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, \ + 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 { 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, \ + 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, \ + 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, Error> { @@ -16,7 +16,7 @@ pub fn path2bytes(p: &Path) -> Result, 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, Error> { fn check(b: Cow<[u8]>) -> Result, 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 { -- cgit v1.2.3