#![allow(unsafe_code)]
use core::ffi::{c_int, c_void};
use magic_sys as libmagic;
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct Cookie(libmagic::magic_t);
impl Cookie {
pub fn new(cookie: &mut Self) -> Self {
Self(cookie.0)
}
}
#[derive(Debug)]
pub(crate) enum CookieError {
Error(Error),
ApiViolation(ApiViolation),
}
impl std::error::Error for CookieError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
CookieError::Error(source) => Some(source),
CookieError::ApiViolation(source) => Some(source),
}
}
}
impl std::fmt::Display for CookieError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
CookieError::Error(err) => write!(f, "cookie error: {0}", err),
CookieError::ApiViolation(violation) => {
write!(f, "cookie API violation: {0}", violation)
}
}
}
}
impl std::convert::From<Error> for CookieError {
fn from(source: Error) -> Self {
CookieError::Error(source)
}
}
impl std::convert::From<ApiViolation> for CookieError {
fn from(source: ApiViolation) -> Self {
CookieError::ApiViolation(source)
}
}
#[derive(Debug)]
pub(crate) struct Error {
explanation: std::ffi::CString,
errno: Option<std::io::Error>,
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Self { explanation, errno } = self;
write!(
f,
"libmagic error ({0}): {1}",
match errno {
Some(errno) => format!("OS errno: {0}", errno),
None => "no OS errno".to_string(),
},
explanation.to_string_lossy(),
)
}
}
#[derive(Debug)]
pub(crate) enum ApiViolation {
MissingError(&'static str),
UnexpectedReturnValue(String),
}
impl std::error::Error for ApiViolation {}
impl std::fmt::Display for ApiViolation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ApiViolation::MissingError(e) => write!(f, "{0}", e),
ApiViolation::UnexpectedReturnValue(e) => write!(f, "{0}", e),
}
}
}
fn last_error(cookie: &Cookie) -> Option<Error> {
let error = unsafe { libmagic::magic_error(cookie.0) };
let errno = unsafe { libmagic::magic_errno(cookie.0) };
if error.is_null() {
None
} else {
let c_str = unsafe { std::ffi::CStr::from_ptr(error) };
Some(Error {
explanation: c_str.into(),
errno: match errno {
0 => None,
_ => Some(std::io::Error::from_raw_os_error(errno)),
},
})
}
}
fn expect_error(cookie: &Cookie, description: &'static str) -> CookieError {
match last_error(cookie) {
Some(error) => error.into(),
None => ApiViolation::MissingError(description).into(),
}
}
pub(crate) fn close(cookie: &mut Cookie) {
unsafe { libmagic::magic_close(cookie.0) }
}
pub(crate) fn file(
cookie: &Cookie,
filename: &std::ffi::CStr, ) -> Result<std::ffi::CString, CookieError> {
let filename_ptr = filename.as_ptr();
let res = unsafe { libmagic::magic_file(cookie.0, filename_ptr) };
if res.is_null() {
Err(expect_error(
cookie,
"`magic_file()` did not set last error",
))
} else {
let c_str = unsafe { std::ffi::CStr::from_ptr(res) };
Ok(c_str.into())
}
}
pub(crate) fn buffer(cookie: &Cookie, buffer: &[u8]) -> Result<std::ffi::CString, CookieError> {
let buffer_ptr = buffer.as_ptr();
let buffer_len = buffer.len();
let res = unsafe { libmagic::magic_buffer(cookie.0, buffer_ptr, buffer_len) };
if res.is_null() {
Err(expect_error(
cookie,
"`magic_buffer()` did not set last error",
))
} else {
let c_str = unsafe { std::ffi::CStr::from_ptr(res) };
Ok(c_str.into())
}
}
pub(crate) fn setflags(cookie: &Cookie, flags: c_int) -> Result<(), SetFlagsError> {
let ret = unsafe { libmagic::magic_setflags(cookie.0, flags) };
match ret {
-1 => Err(SetFlagsError { flags }),
_ => Ok(()),
}
}
#[derive(Debug)]
pub(crate) struct SetFlagsError {
flags: c_int,
}
impl std::error::Error for SetFlagsError {}
impl std::fmt::Display for SetFlagsError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Self { flags } = self;
write!(f, "could not set magic cookie flags {0}", flags)
}
}
pub(crate) fn check(cookie: &Cookie, filename: Option<&std::ffi::CStr>) -> Result<(), CookieError> {
let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr);
let res = unsafe { libmagic::magic_check(cookie.0, filename_ptr) };
match res {
0 => Ok(()),
-1 => Err(expect_error(
cookie,
"`magic_check()` did not set last error",
)),
res => Err(ApiViolation::UnexpectedReturnValue(format!(
"expected 0 or -1 but `magic_check()` returned {}",
res
))
.into()),
}
}
pub(crate) fn compile(
cookie: &Cookie,
filename: Option<&std::ffi::CStr>,
) -> Result<(), CookieError> {
let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr);
let res = unsafe { libmagic::magic_compile(cookie.0, filename_ptr) };
match res {
0 => Ok(()),
-1 => Err(expect_error(
cookie,
"`magic_compile()` did not set last error",
)),
res => Err(ApiViolation::UnexpectedReturnValue(format!(
"Expected 0 or -1 but `magic_compile()` returned {}",
res
))
.into()),
}
}
pub(crate) fn list(cookie: &Cookie, filename: Option<&std::ffi::CStr>) -> Result<(), CookieError> {
let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr);
let res = unsafe { libmagic::magic_list(cookie.0, filename_ptr) };
match res {
0 => Ok(()),
-1 => Err(expect_error(
cookie,
"`magic_list()` did not set last error",
)),
res => Err(ApiViolation::UnexpectedReturnValue(format!(
"Expected 0 or -1 but `magic_list()` returned {}",
res
))
.into()),
}
}
pub(crate) fn load(cookie: &Cookie, filename: Option<&std::ffi::CStr>) -> Result<(), CookieError> {
let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr);
let res = unsafe { libmagic::magic_load(cookie.0, filename_ptr) };
match res {
0 => Ok(()),
-1 => Err(expect_error(
cookie,
"`magic_load()` did not set last error",
)),
res => Err(ApiViolation::UnexpectedReturnValue(format!(
"Expected 0 or -1 but `magic_load()` returned {}",
res
))
.into()),
}
}
pub(crate) fn load_buffers(cookie: &Cookie, buffers: &[&[u8]]) -> Result<(), CookieError> {
let mut ffi_buffers: Vec<*const u8> = Vec::with_capacity(buffers.len());
let mut ffi_sizes = Vec::with_capacity(buffers.len());
let ffi_nbuffers = buffers.len();
for slice in buffers {
ffi_buffers.push((*slice).as_ptr());
ffi_sizes.push(slice.len());
}
let ffi_buffers_ptr = ffi_buffers.as_mut_ptr() as *mut *mut c_void;
let ffi_sizes_ptr = ffi_sizes.as_mut_ptr();
let res = unsafe {
libmagic::magic_load_buffers(cookie.0, ffi_buffers_ptr, ffi_sizes_ptr, ffi_nbuffers)
};
match res {
0 => Ok(()),
-1 => Err(expect_error(
cookie,
"`magic_load_buffers()` did not set last error",
)),
res => Err(ApiViolation::UnexpectedReturnValue(format!(
"Expected 0 or -1 but `magic_load_buffers()` returned {}",
res
))
.into()),
}
}
pub(crate) fn open(flags: c_int) -> Result<Cookie, OpenError> {
let cookie = unsafe { libmagic::magic_open(flags) };
if cookie.is_null() {
Err(OpenError {
flags,
errno: std::io::Error::last_os_error(),
})
} else {
Ok(Cookie(cookie))
}
}
#[derive(Debug)]
pub(crate) struct OpenError {
flags: c_int,
errno: std::io::Error,
}
impl std::error::Error for OpenError {}
impl std::fmt::Display for OpenError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Self { flags, errno } = self;
write!(
f,
"could not open magic cookie with flags {0}: {1}",
flags, errno,
)
}
}
impl OpenError {
pub fn errno(&self) -> &std::io::Error {
&self.errno
}
}
pub(crate) fn version() -> c_int {
unsafe { libmagic::magic_version() }
}
#[cfg(test)]
mod tests {
use super::{ApiViolation, CookieError, Error, OpenError, SetFlagsError};
fn assert_impl_debug<T: std::fmt::Debug>() {}
fn assert_impl_display<T: std::fmt::Display>() {}
fn assert_impl_error<T: std::error::Error>() {}
#[test]
fn error_impls() {
assert_impl_debug::<Error>();
assert_impl_display::<Error>();
assert_impl_error::<Error>();
}
#[test]
fn apiviolation_impls() {
assert_impl_debug::<ApiViolation>();
assert_impl_display::<ApiViolation>();
assert_impl_error::<ApiViolation>();
}
#[test]
fn cookieerror_impls() {
assert_impl_debug::<CookieError>();
assert_impl_display::<CookieError>();
assert_impl_error::<CookieError>();
}
#[test]
fn openerror_impls() {
assert_impl_debug::<OpenError>();
assert_impl_display::<OpenError>();
assert_impl_error::<OpenError>();
}
#[test]
fn setflagserror_impls() {
assert_impl_debug::<SetFlagsError>();
assert_impl_display::<SetFlagsError>();
assert_impl_error::<SetFlagsError>();
}
}