winmmf_ffi/lib.rs
1//! # An FFI interface for WinMMF
2//!
3//! The recommended way of working with this interface is to call [`open`] (or [`new`]) and then immediately [`read`]
4//! with an explicit count of 0. Reuse the pointer provided for all subsequent reads by calling [`read_buf`], and then
5//! freeing it with [`free_result`], most likely with other teardown and exit steps for your program. Alternatively, you
6//! could just bring your own pointers. Just make sure they're all valid.
7//!
8//! If the lifetime for any MMFs should be `&'static` or longer than the execution context (e.g. you only `open` MMFs
9//! created by a system service), it's possible to leave cleanup to the OS. No guarantees are made if or when that
10//! happens, other than that cleanup will be done between program exit and system shutdown. If nobody is using the MMF
11//! anymore, and all relevant programs have finished their `main` function (regardless of exit status), the system
12//! should free up lingering handles with no active processes before erroring on the resource limit getting hit.
13//!
14//! During the lifetime of your program, if you decide to close any MMFs, they will be ejected from the inner
15//! collection. Should you need to reopen one, and you're sure other handles to it yet live in the system, you can open
16//! it anew and your data should be there unchanged.
17//! Should you forget to free a pointer, use [`free_raw`] at your own risk.
18//!
19//! If you need to use several MMFs at once in your code, you can. Beware that tracking their indices is entirely at
20//! your own risk though. Should you know which indices you need, and do you plan to use the same one for extended
21//! periods of time, then select one for use with [`select_default`].
22
23use std::{
24 ffi::{c_char, CStr},
25 num::NonZeroUsize,
26 ptr::null_mut,
27 sync::{
28 atomic::{AtomicUsize, Ordering},
29 Mutex, OnceLock,
30 },
31};
32pub use winmmf::Namespace as ValidNamespaces;
33use winmmf::*;
34
35/// You didn't think I was going to keep _this_ long a type unaliased right?
36type MMFWrapper<'a> = Mutex<Vec<MemoryMappedFile<RWLock<'a>>>>;
37
38/// A wrapper to hold any MMFs that are produced during the application lifetime.
39static MMFS: OnceLock<MMFWrapper> = OnceLock::new();
40/// Currently selected default MMF to operate on. Counting starts from 1.
41static CURRENT: AtomicUsize = AtomicUsize::new(0);
42
43/// Lazy wrapper to use when ensuring initialization
44fn _init<'a>(cap: usize) -> MMFWrapper<'a> {
45 Mutex::new(Vec::with_capacity(cap))
46}
47
48/// Initialize the inner object to hold MMF instances.
49///
50/// Returns: 0 on success, -1 on error.
51/// The only conceivable error state would be calling this function more than once.
52#[unsafe(no_mangle)]
53pub extern "system" fn init(count: Option<NonZeroUsize>) -> isize {
54 let cap = count.map(|c| c.get()).unwrap_or(1);
55 MMFS.set(_init(cap)).map(|_| 0).unwrap_or(-1)
56}
57
58/// Open an existing MMF and push it into the list, returning its index or an error indicator.
59///
60/// There are several possible return values here, these are:
61///
62/// - Positive integers: the new index
63/// - -1: Size is 0
64/// - -2: The name is invalid UTF-8
65/// - -3: The namespace is invalid
66/// - -4: The MMF could not be opened
67/// - -5: The MMF could not be stored
68#[unsafe(no_mangle)]
69pub extern "system" fn open(
70 size: Option<NonZeroUsize>,
71 name: *const c_char,
72 namespace: u8,
73 large_pages: Option<&bool>,
74) -> isize {
75 match (
76 size,
77 unsafe { CStr::from_ptr(name).to_str().ok().map(|s| (s.len() > 0).then(|| s.to_owned())).flatten() },
78 namespace.try_into(),
79 ) {
80 (None, _, _) => -1,
81 (_, None, _) => -2,
82 (_, _, Err(_)) => -3,
83 (Some(size), Some(namestr), Ok(ns)) => {
84 if let Ok(mapped) = MemoryMappedFile::open(size, namestr, ns, false, large_pages.cloned()) {
85 MMFS.get_or_init(|| _init(1))
86 .lock()
87 .map(|mut inner| {
88 inner.push(mapped);
89 let idx = inner.len() - 1;
90 CURRENT.store(idx + 1, Ordering::Relaxed);
91 idx as isize
92 })
93 .unwrap_or(-5)
94 } else {
95 -4
96 }
97 }
98 }
99}
100
101/// Create a new MMF and push it into the list, returning the new index or an error indicator.
102///
103/// There are several possible return values here, these are:
104///
105/// - Positive integers: Success
106/// - -1: Size is 0
107/// - -2: The name is invalid UTF-8
108/// - -3: The namespace is invalid
109/// - -4: The MMF could not be opened
110/// - -5: The MMF could not be stored
111#[unsafe(no_mangle)]
112pub extern "system" fn new(
113 size: Option<NonZeroUsize>,
114 name: *const c_char,
115 namespace: u8,
116 large_pages: Option<&bool>,
117) -> isize {
118 match (
119 size,
120 unsafe { CStr::from_ptr(name).to_str().ok().map(|s| (s.len() > 0).then(|| s.to_owned())).flatten() },
121 namespace.try_into(),
122 ) {
123 (None, _, _) => -1,
124 (_, None, _) => -2,
125 (_, _, Err(_)) => -3,
126 (Some(size), Some(namestr), Ok(ns)) => {
127 if let Ok(mapped) = MemoryMappedFile::new(size, namestr, ns, large_pages.cloned()) {
128 MMFS.get_or_init(|| _init(1))
129 .lock()
130 .map(|mut inner| {
131 inner.push(mapped);
132 let idx = inner.len() - 1;
133 CURRENT.store(idx + 1, Ordering::Relaxed);
134 idx as isize
135 })
136 .unwrap_or(-5)
137 } else {
138 -4
139 }
140 }
141 }
142}
143
144/// Read `count` bytes from the MMF into the provided buffer.
145///
146/// It is up to the caller to ensure the buffer is large enough to hold at least `count` bytes. Passing in a buffer
147/// smaller than `count` from Rust space is undefined behavior. This function _will_ make the assumption the buffer is
148/// exactly `count` items long.
149/// Return values are negative integers for errors, or 0 for success.
150///
151/// # Safety
152/// Ensure `buff` is valid for at least `count` bytes and all will be well.
153///
154/// - -1: No MMFs opened yet
155/// - -2: MMF is closed
156/// - -3: MMF isn't initialized
157/// - -4: ???
158#[unsafe(no_mangle)]
159pub unsafe extern "system" fn read_buf(mmf_idx: Option<NonZeroUsize>, count: usize, buff: *mut u8) -> isize {
160 if buff.is_null() {
161 return -4;
162 }
163 if count == 0 {
164 return 0;
165 }
166 MMFS.get()
167 .map(|inner| {
168 inner
169 .lock()
170 .map(|inner| {
171 inner
172 .get(mmf_idx.map(|nsu| nsu.get()).unwrap_or_else(|| CURRENT.load(Ordering::Acquire) - 1))
173 .map(|mmf| {
174 mmf.read_to_raw(buff, count).map(|_| 0).unwrap_or_else(|e| match e {
175 Error::MMF_NotFound => -2,
176 Error::Uninitialized => -3,
177 _ => -4,
178 })
179 })
180 .unwrap_or(-1)
181 })
182 .unwrap_or(-4)
183 })
184 .unwrap_or(-1)
185}
186
187/// Read `count` bytes or all contents from the MMF and give back a pointer to the data.
188///
189/// The pointer produced from this function **must** be freed using [`free_result`], regardless of error state.
190/// To this end, the returned pointer will _always_ have enough size behind it to fit the entire mapped view. Before
191/// freeing it, this pointer may also be used with [read_buf] so you know you have a safe pointer to work with.
192/// To further support this, passing a `count` of 0 returns a fresh buffer.
193/// If you create more than one buffer, they should _all_ be handled with `free_result`. Failing to do so leaks memory.
194///
195/// If something went wrong, the data behind the pointer will be an error code, right padded with `0xFF` until the end
196/// of the requested buffer. If no size is provided, the returned pointer will be the length of the current active MMF.
197#[unsafe(no_mangle)]
198pub extern "system" fn read(mmf_idx: Option<NonZeroUsize>, count: usize) -> *mut u8 {
199 MMFS.get()
200 .map(|inner| {
201 inner
202 .lock()
203 .map(|inner| {
204 inner
205 .get(mmf_idx.map(|nsu| nsu.get()).unwrap_or_else(|| CURRENT.load(Ordering::Acquire) - 1))
206 .map(|mmf| {
207 if count == 0 {
208 let mut ret = vec![0; mmf.size()];
209 ret.shrink_to_fit();
210 let ptr = ret.as_mut_ptr();
211 std::mem::forget(ret);
212 ptr
213 } else {
214 let mut ret = Vec::new();
215 let ptr = ret.as_mut_ptr();
216
217 match mmf.read_to_buf(&mut ret, count) {
218 Ok(_) => {
219 std::mem::forget(ret);
220 ptr
221 } /* Becomes a pointer to the first */
222 // element in the vec
223 Err(e) => {
224 let val = match e {
225 Error::MMF_NotFound => -2_i8,
226 Error::Uninitialized => -3_i8,
227 _ => -4_i8,
228 };
229 ret = vec![0xFF; mmf.size()];
230 ret[0] = val as u8;
231 ret.shrink_to_fit();
232 std::mem::forget(ret);
233 ptr
234 }
235 }
236 }
237 })
238 .unwrap_or(null_mut())
239 })
240 .unwrap_or(null_mut())
241 })
242 .unwrap_or(null_mut())
243}
244
245/// Free a pointer used for reading from an MMF by its index number.
246///
247/// # Safety
248/// Do not pass pointers not received from this library. Doing so is UB by definition.
249/// Null pointers will be silently ignored.
250///
251/// Mismatching pointers and their MMFs will leak memory. But because users tend to mess up, this function doesn't close
252/// the MMF to prevent worse things from happening. Such as use after free and dangling references.
253/// Using the pointer after passing it through this function is UB.
254#[unsafe(no_mangle)]
255pub unsafe extern "system" fn free_result(mmf_idx: Option<NonZeroUsize>, res: *mut u8) {
256 if res.is_null() {
257 return;
258 }
259 MMFS.get()
260 .map(|inner| {
261 inner
262 .lock()
263 .map(|inner| {
264 inner
265 .get(mmf_idx.map(|nsu| nsu.get()).unwrap_or_else(|| CURRENT.load(Ordering::Acquire) - 1))
266 .map(|mmf| unsafe { free_raw(res, mmf.size()) })
267 .unwrap_or(())
268 })
269 .unwrap_or(())
270 })
271 .unwrap_or(())
272}
273
274/// You had better know how big that thing is.
275///
276/// # Safety
277///
278/// If the provided size is incorrect, you might be leaking bytes (too small, mostly harmless) or you might be invoking
279/// UB (too large, harmful to the universe). If you're just gambling the size, I hope you anger the Duolingo bird.
280#[unsafe(no_mangle)]
281pub unsafe extern "system" fn free_raw(res: *mut u8, size: usize) {
282 drop(Vec::from_raw_parts(res, size, size))
283}
284
285/// Expose writing data as well. Slightly less complex for FFI purposes than reading.
286///
287/// # Safety
288/// `data` must be at least `count` bytes long, or somebody's getting hurt.
289///
290/// Return values for this function are:
291/// - 0: Write was successful!
292/// - -1: Writing not allowed (readonly or closed)
293/// - -2: Buffer is bigger than the MMF
294/// - -3: Uninitialized
295/// - -4: Read- or WriteLocked
296/// - -5: Programmer issue
297#[unsafe(no_mangle)]
298pub unsafe extern "system" fn write(mmf_idx: Option<NonZeroUsize>, data: *mut u8, size: usize) -> isize {
299 if data.is_null() {
300 -5
301 } else if size > 0 {
302 MMFS.get()
303 .map(|inner| {
304 inner
305 .lock()
306 .map(|inner| {
307 inner
308 .get(mmf_idx.map(|nsu| nsu.get()).unwrap_or_else(|| CURRENT.load(Ordering::Acquire) - 1))
309 .map(|mmf| {
310 let buff = unsafe { std::slice::from_raw_parts_mut(data, size) };
311 match mmf.write(buff) {
312 Ok(_) => 0,
313 Err(Error::MMF_NotFound) => -1,
314 Err(Error::NotEnoughMemory) => -2,
315 Err(Error::Uninitialized) => -3,
316 Err(Error::ReadLocked) | Err(Error::WriteLocked) => -4,
317 _ => -5,
318 }
319 })
320 .unwrap_or(-3)
321 })
322 .unwrap_or(-5)
323 })
324 .unwrap_or(-5)
325 } else {
326 0 // Copying zero bytes is always successful.
327 }
328}
329
330/// Convenience function to open a read-only MMF and get an index back to read from it.
331///
332/// Results returned from this function are the same as [`open`]
333#[unsafe(no_mangle)]
334pub extern "system" fn open_ro(
335 size: Option<NonZeroUsize>,
336 name: *const c_char,
337 namespace: u8,
338 large_pages: Option<&bool>,
339) -> isize {
340 match (
341 size,
342 unsafe { CStr::from_ptr(name).to_str().ok().map(|s| (s.len() > 0).then(|| s.to_owned())).flatten() },
343 namespace.try_into(),
344 ) {
345 (None, _, _) => -1,
346 (_, None, _) => -2,
347 (_, _, Err(_)) => -3,
348 (Some(size), Some(namestr), Ok(ns)) => {
349 if let Ok(mapped) = MemoryMappedFile::open(size, namestr, ns, true, large_pages.cloned()) {
350 MMFS.get_or_init(|| _init(1))
351 .lock()
352 .map(|mut inner| {
353 inner.push(mapped);
354 let idx = inner.len() - 1;
355 CURRENT.store(idx + 1, Ordering::Relaxed);
356 idx as isize
357 })
358 .unwrap_or(-5)
359 } else {
360 -4
361 }
362 }
363 }
364}
365
366/// Close the MMF
367///
368/// Closes the specific instance stored here without interferring with other processes that might be using it.
369#[unsafe(no_mangle)]
370pub extern "system" fn close(mmf_idx: Option<NonZeroUsize>) {
371 MMFS.get()
372 .map(|inner| {
373 inner
374 .lock()
375 .map(|mut inner| {
376 drop(
377 inner.remove(
378 mmf_idx.map(|nsu| nsu.get()).unwrap_or_else(|| CURRENT.load(Ordering::Acquire) - 1),
379 ),
380 )
381 })
382 .unwrap_or_default()
383 })
384 .unwrap_or_default()
385}
386
387/// Selects a different default mmf for all operations.
388///
389/// The only thing that can really go wrong here is the user being dumb,
390/// so the input is clamped or wrapped and the newly selected index is returned.
391#[unsafe(no_mangle)]
392pub extern "system" fn select_default(mmf_idx: Option<NonZeroUsize>) -> isize {
393 if let Some(max_idx) = MMFS.get().map(|inner| inner.lock().unwrap().len()) {
394 let idx = mmf_idx.map(|nsu| nsu.get()).unwrap_or(CURRENT.load(Ordering::Acquire));
395 let select = if idx == 0 || idx > max_idx { max_idx } else { idx };
396 CURRENT.store(select, Ordering::Release);
397 return select as isize;
398 }
399 -1
400}