1#![allow(unsafe_code)]
9
10use magic_sys as libmagic;
11
12#[derive(Debug)]
13#[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#[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
73pub(crate) fn file(
77 cookie: &Cookie,
78 filename: &std::ffi::CStr, ) -> 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
94pub(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
127pub(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
147pub(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
170pub(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
190pub(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
210pub(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 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}