goods_treasury_import_ffi/
lib.rs

1use {
2    goods_treasury_import::{Importer, Registry},
3    std::path::Path,
4    uuid::Uuid,
5};
6
7const BUFFER_LEN: usize = 1024;
8
9#[repr(C)]
10#[derive(Clone, Copy)]
11pub struct ImporterOpaque {
12    _byte: u8,
13}
14
15#[repr(C)]
16pub struct ImporterFFI {
17    data: *const ImporterOpaque,
18    name: unsafe fn(*const ImporterOpaque, *mut u8, usize) -> usize,
19    source: unsafe fn(*const ImporterOpaque, *mut u8, usize) -> usize,
20    native: unsafe fn(*const ImporterOpaque, *mut u8, usize) -> usize,
21
22    #[cfg(any(unix, target_os = "wasi"))]
23    import: unsafe fn(
24        *const ImporterOpaque,
25        *const u8,
26        usize,
27        *const u8,
28        usize,
29        *mut u8,
30        usize,
31    ) -> isize,
32
33    #[cfg(windows)]
34    import: unsafe fn(
35        *const ImporterOpaque,
36        *const u16,
37        usize,
38        *const u16,
39        usize,
40        *mut u8,
41        usize,
42    ) -> isize,
43}
44
45impl ImporterFFI {
46    pub fn new<I>(importer: &'static I) -> Self
47    where
48        I: Importer,
49    {
50        ImporterFFI {
51            data: importer as *const I as *const ImporterOpaque,
52            name: |data, buf, len| unsafe {
53                let name = (*(data as *mut I)).name();
54                let buf = std::slice::from_raw_parts_mut(buf, len);
55                buf[..len.min(name.len())].copy_from_slice(&name.as_bytes());
56                name.len()
57            },
58            source: |data, buf, len| unsafe {
59                let source = (*(data as *mut I)).source();
60                let buf = std::slice::from_raw_parts_mut(buf, len);
61                buf[..len.min(source.len())].copy_from_slice(&source.as_bytes());
62                source.len()
63            },
64            native: |data, buf, len| unsafe {
65                let native = (*(data as *mut I)).native();
66                let buf = std::slice::from_raw_parts_mut(buf, len);
67                buf[..len.min(native.len())].copy_from_slice(&native.as_bytes());
68                native.len()
69            },
70            import: |data, source_ptr, source_len, native_ptr, native_len, error_ptr, error_len| unsafe {
71                #[cfg(any(unix, target_os = "wasi"))]
72                let (source, native) = {
73                    use std::ffi::OsStr;
74
75                    #[cfg(unix)]
76                    use std::os::unix::ffi::OsStrExt;
77                    #[cfg(target_os = "wasi")]
78                    use std::os::wasi::ffi::OsStrExt;
79
80                    let source =
81                        OsStr::from_bytes(std::slice::from_raw_parts(source_ptr, source_len));
82
83                    let native =
84                        OsStr::from_bytes(std::slice::from_raw_parts(native_ptr, native_len));
85
86                    (source, native)
87                };
88
89                #[cfg(windows)]
90                let (source, native) = {
91                    use std::{ffi::OsString, os::windows::ffi::OsStringExt};
92
93                    let source =
94                        OsString::from_wide(std::slice::from_raw_parts(source_ptr, source_len));
95
96                    let native =
97                        OsString::from_wide(std::slice::from_raw_parts(native_ptr, native_len));
98
99                    (source, native)
100                };
101
102                match (*(data as *const I)).import(
103                    source.as_ref(),
104                    native.as_ref(),
105                    &mut RegistryFFI,
106                ) {
107                    Ok(()) => 0,
108                    Err(err) => {
109                        use std::io::{Cursor, Write as _};
110                        let error_slice = std::slice::from_raw_parts_mut(error_ptr, error_len);
111                        let mut error_write = Cursor::new(error_slice);
112
113                        let _ = write!(error_write, "{:#}", err);
114                        -(error_write.position() as isize)
115                    }
116                }
117            },
118        }
119    }
120}
121
122struct RegistryFFI;
123
124impl Registry for RegistryFFI {
125    fn store(
126        &mut self,
127        source: &Path,
128        source_format: &str,
129        native_format: &str,
130        tags: &[&str],
131    ) -> eyre::Result<Uuid> {
132        use std::ffi::OsStr;
133
134        #[cfg(unix)]
135        use std::os::unix::ffi::OsStrExt;
136        #[cfg(target_os = "wasi")]
137        use std::os::wasi::ffi::OsStrExt;
138        #[cfg(windows)]
139        use std::os::windows::ffi::OsStrExt;
140
141        let source: &OsStr = source.as_ref();
142
143        #[cfg(any(unix, target_os = "wasi"))]
144        let source = source.as_bytes();
145
146        #[cfg(windows)]
147        let source = source.encode_wide().collect::<Vec<u16>>();
148
149        #[cfg(windows)]
150        let source = String::from_utf16(&source[..]).unwrap();
151
152        let mut result_array = [0u8; BUFFER_LEN];
153
154        let tag_count = tags.len();
155        let tag_ptrs = tags.iter().map(|t| str::as_ptr(t)).collect::<Vec<_>>();
156        let tag_lens = tags.iter().map(|t| str::len(t)).collect::<Vec<_>>();
157
158        let result = unsafe {
159            treasury_registry_store(
160                source.as_ptr(),
161                source.len(),
162                source_format.as_ptr(),
163                source_format.len(),
164                native_format.as_ptr(),
165                native_format.len(),
166                tag_ptrs.as_ptr(),
167                tag_lens.as_ptr(),
168                tag_count,
169                result_array.as_mut_ptr(),
170                BUFFER_LEN,
171            )
172        };
173
174        if result < 0 {
175            let len = result.abs() as usize;
176            let error = std::str::from_utf8(&result_array[..len.min(BUFFER_LEN)]).unwrap();
177            Err(eyre::eyre!("{}", error))
178        } else {
179            // success
180            debug_assert_eq!(result, 16);
181            let uuid = Uuid::from_slice(&result_array[..16]).unwrap();
182            Ok(uuid)
183        }
184    }
185
186    fn fetch(&mut self, asset: &Uuid) -> eyre::Result<Box<Path>> {
187        let asset = asset.as_bytes();
188
189        let mut path_array = [0; BUFFER_LEN];
190        let mut error_array = [0; BUFFER_LEN];
191
192        let result = unsafe {
193            treasury_registry_fetch(
194                asset.as_ptr(),
195                path_array.as_mut_ptr(),
196                BUFFER_LEN,
197                error_array.as_mut_ptr(),
198                BUFFER_LEN,
199            )
200        };
201
202        if result < 0 {
203            let len = result.abs() as usize;
204            let error = std::str::from_utf8(&error_array[..len.min(BUFFER_LEN)]).unwrap();
205            Err(eyre::eyre!("{}", error))
206        } else {
207            let len = result as usize;
208            debug_assert!(len <= BUFFER_LEN);
209
210            #[cfg(any(unix, target_os = "wasi"))]
211            {
212                use std::ffi::OsStr;
213
214                #[cfg(unix)]
215                use std::os::unix::ffi::OsStrExt;
216                #[cfg(target_os = "wasi")]
217                use std::os::wasi::ffi::OsStrExt;
218
219                let path = OsStr::from_bytes(&path_array[..len.min(BUFFER_LEN)]);
220
221                Ok(Path::new(path).into())
222            }
223
224            #[cfg(windows)]
225            {
226                use std::path::PathBuf;
227                let path = std::str::from_utf8(&path_array[..len.min(BUFFER_LEN)]).unwrap();
228                Ok(PathBuf::from(path).into_boxed_path())
229            }
230        }
231    }
232}
233
234extern "C" {
235    fn treasury_registry_store(
236        source_ptr: *const u8,
237        source_len: usize,
238        source_format_ptr: *const u8,
239        source_format_len: usize,
240        native_format_ptr: *const u8,
241        native_format_len: usize,
242        tag_ptrs: *const *const u8,
243        tag_lens: *const usize,
244        tag_count: usize,
245        result_ptr: *mut u8,
246        result_len: usize,
247    ) -> isize;
248
249    fn treasury_registry_fetch(
250        uuid: *const u8,
251        path_ptr: *mut u8,
252        path_len: usize,
253        error_ptr: *mut u8,
254        error_len: usize,
255    ) -> isize;
256}
257
258#[doc(hidden)]
259#[macro_export]
260macro_rules! count_tt {
261    () => { 0 };
262    ($head:tt $(, $tail:tt)*) => { 1 + $crate::count_tt!($($tail),*) };
263}
264
265/// Generates FFI-safe function to enumerate importers
266#[macro_export]
267macro_rules! generate_imports_and_exports {
268    ($($importer:expr),* $(,)?) => {
269
270        #[no_mangle]
271        pub unsafe fn treasury_importer_enumerate_importers(importers: *mut $crate::ImporterFFI, count: usize) -> usize {
272            const COUNT: usize = $crate::count_tt!($(($importer)),*);
273
274            if count < COUNT {
275                COUNT
276            } else {
277                let mut ptr = importers;
278
279                $(
280                    ::std::ptr::write(ptr, $crate::ImporterFFI::new($importer));
281                    ptr = ptr.add(1);
282                )*
283
284                COUNT
285            }
286        }
287    };
288}
289
290#[cfg(target_os = "wasi")]
291#[no_mangle]
292pub unsafe fn treasury_importer_name_source_native_trampoline(
293    function: unsafe fn(*const ImporterOpaque, *mut u8, u32) -> u32,
294    ptr: *const ImporterOpaque,
295    result_ptr: *mut u8,
296    result_len: u32,
297) -> u32 {
298    function(ptr, result_ptr, result_len)
299}
300
301#[cfg(target_os = "wasi")]
302#[no_mangle]
303pub unsafe fn treasury_importer_import_trampoline(
304    function: unsafe fn(*const ImporterOpaque, *const u8, u32, *const u8, u32, *mut u8, u32) -> i32,
305    ptr: *const ImporterOpaque,
306    source_path_ptr: *const u8,
307    source_path_len: u32,
308    native_path_ptr: *const u8,
309    native_path_len: u32,
310    error_ptr: *mut u8,
311    error_len: u32,
312) -> i32 {
313    function(
314        ptr,
315        source_path_ptr,
316        source_path_len,
317        native_path_ptr,
318        native_path_len,
319        error_ptr,
320        error_len,
321    )
322}
323
324/// # Safety
325///
326/// This function is export of standard function `alloc::alloc::alloc`.
327/// Same safety principles applies.
328#[no_mangle]
329pub unsafe fn treasury_importer_alloc(size: usize, align: usize) -> *mut u8 {
330    let layout = std::alloc::Layout::from_size_align(size, align).unwrap();
331    std::alloc::alloc(layout)
332}
333
334/// # Safety
335///
336/// This function is export of standard function `alloc::alloc::dealloc`.
337/// Same safety principles applies.
338#[no_mangle]
339pub unsafe fn treasury_importer_dealloc(ptr: *mut u8, size: usize, align: usize) {
340    let layout = std::alloc::Layout::from_size_align(size, align).unwrap();
341    std::alloc::dealloc(ptr, layout);
342}