Skip to main content

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}