Expose more error information

This commit is contained in:
Steven Fackler 2017-02-19 16:05:58 -08:00
parent 618cc70d19
commit 268288337b
2 changed files with 82 additions and 16 deletions

View File

@ -143,6 +143,9 @@ pub const BIO_FLAGS_SHOULD_RETRY: c_int = 0x08;
pub const CRYPTO_LOCK: c_int = 1; pub const CRYPTO_LOCK: c_int = 1;
pub const ERR_TXT_MALLOCED: c_int = 0x01;
pub const ERR_TXT_STRING: c_int = 0x02;
pub const ERR_LIB_PEM: c_int = 9; pub const ERR_LIB_PEM: c_int = 9;
pub const PEM_R_NO_START_LINE: c_int = 108; pub const PEM_R_NO_START_LINE: c_int = 108;
@ -1542,6 +1545,7 @@ extern {
pub fn ERR_peek_last_error() -> c_ulong; pub fn ERR_peek_last_error() -> c_ulong;
pub fn ERR_get_error() -> c_ulong; pub fn ERR_get_error() -> c_ulong;
pub fn ERR_get_error_line_data(file: *mut *const c_char, line: *mut c_int, data: *mut *const c_char, flags: *mut c_int) -> c_ulong;
pub fn ERR_lib_error_string(err: c_ulong) -> *const c_char; pub fn ERR_lib_error_string(err: c_ulong) -> *const c_char;
pub fn ERR_func_error_string(err: c_ulong) -> *const c_char; pub fn ERR_func_error_string(err: c_ulong) -> *const c_char;
pub fn ERR_reason_error_string(err: c_ulong) -> *const c_char; pub fn ERR_reason_error_string(err: c_ulong) -> *const c_char;

View File

@ -1,9 +1,11 @@
use libc::c_ulong; use libc::{c_ulong, c_char, c_int};
use std::fmt; use std::fmt;
use std::error; use std::error;
use std::ffi::CStr; use std::ffi::CStr;
use std::io; use std::io;
use std::str; use std::str;
use std::ptr;
use std::borrow::Cow;
use ffi; use ffi;
@ -62,28 +64,63 @@ impl From<ErrorStack> for fmt::Error {
/// An error reported from OpenSSL. /// An error reported from OpenSSL.
#[derive(Clone)] #[derive(Clone)]
pub struct Error(c_ulong); pub struct Error {
code: c_ulong,
file: *const c_char,
line: c_int,
data: Option<Cow<'static, str>>,
}
unsafe impl Sync for Error {}
unsafe impl Send for Error {}
impl Error { impl Error {
/// Returns the first error on the OpenSSL error stack. /// Returns the first error on the OpenSSL error stack.
pub fn get() -> Option<Error> { pub fn get() -> Option<Error> {
unsafe {
ffi::init(); ffi::init();
match unsafe { ffi::ERR_get_error() } { let mut file = ptr::null();
let mut line = 0;
let mut data = ptr::null();
let mut flags = 0;
match ffi::ERR_get_error_line_data(&mut file, &mut line, &mut data, &mut flags) {
0 => None, 0 => None,
err => Some(Error(err)), code => {
// The memory referenced by data is only valid until that slot is overwritten
// in the error stack, so we'll need to copy it off if it's dynamic
let data = if flags & ffi::ERR_TXT_STRING != 0 {
let bytes = CStr::from_ptr(data as *const _).to_bytes();
let data = str::from_utf8(bytes).unwrap();
let data = if flags & ffi::ERR_TXT_MALLOCED != 0 {
Cow::Owned(data.to_string())
} else {
Cow::Borrowed(data)
};
Some(data)
} else {
None
};
Some(Error {
code: code,
file: file,
line: line,
data: data,
})
}
}
} }
} }
/// Returns the raw OpenSSL error code for this error. /// Returns the raw OpenSSL error code for this error.
pub fn code(&self) -> c_ulong { pub fn code(&self) -> c_ulong {
self.0 self.code
} }
/// Returns the name of the library reporting the error, if available. /// Returns the name of the library reporting the error, if available.
pub fn library(&self) -> Option<&'static str> { pub fn library(&self) -> Option<&'static str> {
unsafe { unsafe {
let cstr = ffi::ERR_lib_error_string(self.0); let cstr = ffi::ERR_lib_error_string(self.code);
if cstr.is_null() { if cstr.is_null() {
return None; return None;
} }
@ -95,7 +132,7 @@ impl Error {
/// Returns the name of the function reporting the error. /// Returns the name of the function reporting the error.
pub fn function(&self) -> Option<&'static str> { pub fn function(&self) -> Option<&'static str> {
unsafe { unsafe {
let cstr = ffi::ERR_func_error_string(self.0); let cstr = ffi::ERR_func_error_string(self.code);
if cstr.is_null() { if cstr.is_null() {
return None; return None;
} }
@ -107,7 +144,7 @@ impl Error {
/// Returns the reason for the error. /// Returns the reason for the error.
pub fn reason(&self) -> Option<&'static str> { pub fn reason(&self) -> Option<&'static str> {
unsafe { unsafe {
let cstr = ffi::ERR_reason_error_string(self.0); let cstr = ffi::ERR_reason_error_string(self.code);
if cstr.is_null() { if cstr.is_null() {
return None; return None;
} }
@ -115,6 +152,25 @@ impl Error {
Some(str::from_utf8(bytes).unwrap()) Some(str::from_utf8(bytes).unwrap())
} }
} }
/// Returns the name of the source file which encountered the error.
pub fn file(&self) -> &'static str {
unsafe {
assert!(!self.file.is_null());
let bytes = CStr::from_ptr(self.file as *const _).to_bytes();
str::from_utf8(bytes).unwrap()
}
}
/// Returns the line in the source file which encountered the error.
pub fn line(&self) -> c_int {
self.line
}
/// Returns additional data describing the error.
pub fn data(&self) -> Option<&str> {
self.data.as_ref().map(|s| &**s)
}
} }
impl fmt::Debug for Error { impl fmt::Debug for Error {
@ -130,30 +186,36 @@ impl fmt::Debug for Error {
if let Some(reason) = self.reason() { if let Some(reason) = self.reason() {
builder.field("reason", &reason); builder.field("reason", &reason);
} }
builder.field("file", &self.file());
builder.field("line", &self.line());
if let Some(data) = self.data() {
builder.field("data", &data);
}
builder.finish() builder.finish()
} }
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
try!(write!(fmt, "error:{:08X}", self.0)); try!(write!(fmt, "error:{:08X}", self.code()));
match self.library() { match self.library() {
Some(l) => try!(write!(fmt, ":{}", l)), Some(l) => try!(write!(fmt, ":{}", l)),
None => try!(write!(fmt, ":lib({})", ffi::ERR_GET_LIB(self.0))), None => try!(write!(fmt, ":lib({})", ffi::ERR_GET_LIB(self.code()))),
} }
match self.function() { match self.function() {
Some(f) => try!(write!(fmt, ":{}", f)), Some(f) => try!(write!(fmt, ":{}", f)),
None => try!(write!(fmt, ":func({})", ffi::ERR_GET_FUNC(self.0))), None => try!(write!(fmt, ":func({})", ffi::ERR_GET_FUNC(self.code()))),
} }
match self.reason() { match self.reason() {
Some(r) => write!(fmt, ":{}", r), Some(r) => try!(write!(fmt, ":{}", r)),
None => write!(fmt, ":reason({})", ffi::ERR_GET_FUNC(self.0)), None => try!(write!(fmt, ":reason({})", ffi::ERR_GET_FUNC(self.code()))),
} }
write!(fmt, ":{}:{}:{}", self.file(), self.line(), self.data().unwrap_or(""))
} }
} }
impl error::Error for Error { impl error::Error for Error {
fn description(&self) -> &str { fn description(&self) -> &str {
"An OpenSSL error" "an OpenSSL error"
} }
} }