magic/
ffi.rs

1// SPDX-FileCopyrightText: © The `magic` Rust crate authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Internal Foreign Function Interface module for `magic_sys` / `libmagic`
5//!
6//! Contains `unsafe` as a medium level binding.
7
8#![allow(unsafe_code)]
9
10use magic_sys as libmagic;
11
12#[derive(Debug)]
13// non-copy wrapper around raw pointer
14#[repr(transparent)]
15pub(crate) struct Cookie(libmagic::magic_t);
16
17impl Cookie {
18    pub fn new(cookie: &mut Self) -> Self {
19        Self(cookie.0)
20    }
21}
22
23/// Error for opened `magic_t` instance
24#[derive(thiserror::Error, Debug)]
25#[error("magic cookie error ({}): {}",
26match .errno {
27    Some(errno) => format!("OS errno: {}", errno),
28    None => "no OS errno".to_string(),
29},
30.explanation.to_string_lossy()
31)]
32pub(crate) struct CookieError {
33    explanation: std::ffi::CString,
34    errno: Option<std::io::Error>,
35}
36
37fn last_error(cookie: &Cookie) -> Option<CookieError> {
38    let error = unsafe { libmagic::magic_error(cookie.0) };
39    let errno = unsafe { libmagic::magic_errno(cookie.0) };
40
41    if error.is_null() {
42        None
43    } else {
44        let c_str = unsafe { std::ffi::CStr::from_ptr(error) };
45        Some(CookieError {
46            explanation: c_str.into(),
47            errno: match errno {
48                0 => None,
49                _ => Some(std::io::Error::from_raw_os_error(errno)),
50            },
51        })
52    }
53}
54
55fn api_violation(cookie: &Cookie, description: String) -> ! {
56    panic!(
57        "`libmagic` API violation for magic cookie {:?}: {}",
58        cookie, description
59    );
60}
61
62fn expect_error(cookie: &Cookie, description: String) -> CookieError {
63    match last_error(cookie) {
64        Some(err) => err,
65        _ => api_violation(cookie, description),
66    }
67}
68
69pub(crate) fn close(cookie: &mut Cookie) {
70    unsafe { libmagic::magic_close(cookie.0) }
71}
72
73/// # Panics
74///
75/// Panics if `libmagic` violates its API contract, e.g. by not setting the last error.
76pub(crate) fn file(
77    cookie: &Cookie,
78    filename: &std::ffi::CStr, // TODO: Support NULL
79) -> Result<std::ffi::CString, CookieError> {
80    let filename_ptr = filename.as_ptr();
81    let res = unsafe { libmagic::magic_file(cookie.0, filename_ptr) };
82
83    if res.is_null() {
84        Err(expect_error(
85            cookie,
86            "`magic_file()` did not set last error".to_string(),
87        ))
88    } else {
89        let c_str = unsafe { std::ffi::CStr::from_ptr(res) };
90        Ok(c_str.into())
91    }
92}
93
94/// # Panics
95///
96/// Panics if `libmagic` violates its API contract, e.g. by not setting the last error.
97pub(crate) fn buffer(cookie: &Cookie, buffer: &[u8]) -> Result<std::ffi::CString, CookieError> {
98    let buffer_ptr = buffer.as_ptr();
99    let buffer_len = buffer.len() as libc::size_t;
100    let res = unsafe { libmagic::magic_buffer(cookie.0, buffer_ptr, buffer_len) };
101
102    if res.is_null() {
103        Err(expect_error(
104            cookie,
105            "`magic_buffer()` did not set last error".to_string(),
106        ))
107    } else {
108        let c_str = unsafe { std::ffi::CStr::from_ptr(res) };
109        Ok(c_str.into())
110    }
111}
112
113pub(crate) fn setflags(cookie: &Cookie, flags: libc::c_int) -> Result<(), SetFlagsError> {
114    let ret = unsafe { libmagic::magic_setflags(cookie.0, flags) };
115    match ret {
116        -1 => Err(SetFlagsError { flags }),
117        _ => Ok(()),
118    }
119}
120
121#[derive(thiserror::Error, Debug)]
122#[error("could not set magic cookie flags {}", .flags)]
123pub(crate) struct SetFlagsError {
124    flags: libc::c_int,
125}
126
127/// # Panics
128///
129/// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
130pub(crate) fn check(cookie: &Cookie, filename: Option<&std::ffi::CStr>) -> Result<(), CookieError> {
131    let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr);
132    let res = unsafe { libmagic::magic_check(cookie.0, filename_ptr) };
133
134    match res {
135        0 => Ok(()),
136        -1 => Err(expect_error(
137            cookie,
138            "`magic_check()` did not set last error".to_string(),
139        )),
140        res => api_violation(
141            cookie,
142            format!("expected 0 or -1 but `magic_check()` returned {}", res),
143        ),
144    }
145}
146
147/// # Panics
148///
149/// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
150pub(crate) fn compile(
151    cookie: &Cookie,
152    filename: Option<&std::ffi::CStr>,
153) -> Result<(), CookieError> {
154    let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr);
155    let res = unsafe { libmagic::magic_compile(cookie.0, filename_ptr) };
156
157    match res {
158        0 => Ok(()),
159        -1 => Err(expect_error(
160            cookie,
161            "`magic_compile()` did not set last error".to_string(),
162        )),
163        res => api_violation(
164            cookie,
165            format!("Expected 0 or -1 but `magic_compile()` returned {}", res),
166        ),
167    }
168}
169
170/// # Panics
171///
172/// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
173pub(crate) fn list(cookie: &Cookie, filename: Option<&std::ffi::CStr>) -> Result<(), CookieError> {
174    let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr);
175    let res = unsafe { libmagic::magic_list(cookie.0, filename_ptr) };
176
177    match res {
178        0 => Ok(()),
179        -1 => Err(expect_error(
180            cookie,
181            "`magic_list()` did not set last error".to_string(),
182        )),
183        res => api_violation(
184            cookie,
185            format!("Expected 0 or -1 but `magic_list()` returned {}", res),
186        ),
187    }
188}
189
190/// # Panics
191///
192/// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
193pub(crate) fn load(cookie: &Cookie, filename: Option<&std::ffi::CStr>) -> Result<(), CookieError> {
194    let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr);
195    let res = unsafe { libmagic::magic_load(cookie.0, filename_ptr) };
196
197    match res {
198        0 => Ok(()),
199        -1 => Err(expect_error(
200            cookie,
201            "`magic_load()` did not set last error".to_string(),
202        )),
203        res => api_violation(
204            cookie,
205            format!("Expected 0 or -1 but `magic_load()` returned {}", res),
206        ),
207    }
208}
209
210/// # Panics
211///
212/// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data.
213pub(crate) fn load_buffers(cookie: &Cookie, buffers: &[&[u8]]) -> Result<(), CookieError> {
214    let mut ffi_buffers: Vec<*const u8> = Vec::with_capacity(buffers.len());
215    let mut ffi_sizes: Vec<libc::size_t> = Vec::with_capacity(buffers.len());
216    let ffi_nbuffers = buffers.len() as libc::size_t;
217
218    for slice in buffers {
219        ffi_buffers.push((*slice).as_ptr());
220        ffi_sizes.push(slice.len() as libc::size_t);
221    }
222
223    let ffi_buffers_ptr = ffi_buffers.as_mut_ptr() as *mut *mut libc::c_void;
224    let ffi_sizes_ptr = ffi_sizes.as_mut_ptr();
225
226    let res = unsafe {
227        libmagic::magic_load_buffers(cookie.0, ffi_buffers_ptr, ffi_sizes_ptr, ffi_nbuffers)
228    };
229
230    match res {
231        0 => Ok(()),
232        -1 => Err(expect_error(
233            cookie,
234            "`magic_load_buffers()` did not set last error".to_string(),
235        )),
236        res => api_violation(
237            cookie,
238            format!(
239                "Expected 0 or -1 but `magic_load_buffers()` returned {}",
240                res
241            ),
242        ),
243    }
244}
245
246pub(crate) fn open(flags: libc::c_int) -> Result<Cookie, OpenError> {
247    let cookie = unsafe { libmagic::magic_open(flags) };
248
249    if cookie.is_null() {
250        Err(OpenError {
251            flags,
252            // note that libmagic only really cares about MAGIC_PRESERVE_ATIME
253            // invalid bits in `flags` still result in a valid cookie pointer
254            errno: std::io::Error::last_os_error(),
255        })
256    } else {
257        Ok(Cookie(cookie))
258    }
259}
260
261#[derive(thiserror::Error, Debug)]
262#[error("could not open magic cookie with flags {}: {}", .flags, .errno)]
263pub(crate) struct OpenError {
264    flags: libc::c_int,
265    errno: std::io::Error,
266}
267
268impl OpenError {
269    pub fn errno(&self) -> &std::io::Error {
270        &self.errno
271    }
272}
273
274pub(crate) fn version() -> libc::c_int {
275    unsafe { libmagic::magic_version() }
276}