zsh_module/
export_module.rs

1use std::{
2    ffi::{c_char, c_int, CStr},
3    sync::atomic::AtomicBool,
4};
5
6use crate::{log, options::Opts, to_cstr, AnyError, MaybeError, Module};
7
8use parking_lot::Mutex;
9use zsh_sys as zsys;
10
11struct ModuleHolder {
12    module: Mutex<Option<Module>>,
13    panicked: AtomicBool,
14}
15
16impl ModuleHolder {
17    const fn empty() -> Self {
18        Self {
19            module: parking_lot::const_mutex(None),
20            panicked: AtomicBool::new(false),
21        }
22    }
23}
24
25// This struct is neither of them, but since it isn't exposed to user code
26// and it isn't given to any threads, this should be safe.
27unsafe impl Sync for ModuleHolder {}
28unsafe impl Send for ModuleHolder {}
29
30static MODULE: ModuleHolder = ModuleHolder::empty();
31
32unsafe fn strings_from_ptr<'a>(mut ptr: *const *const c_char) -> Vec<&'a str> {
33    let mut vec = Vec::with_capacity(2);
34    loop {
35        if (*ptr).is_null() {
36            break vec;
37        }
38        vec.push(CStr::from_ptr(*ptr).to_str().expect("Failed to parse arg"));
39        ptr = ptr.add(1);
40    }
41}
42
43extern "C" fn builtin_callback(
44    name: *mut c_char,
45    args: *mut *mut c_char,
46    opts: *mut zsys::options,
47    _: i32,
48) -> i32 {
49    handle_panic(|| {
50        let args = unsafe { strings_from_ptr(std::mem::transmute(args)) };
51        let name = unsafe { CStr::from_ptr(name) };
52        let opts = unsafe { Opts::from_raw(opts) };
53
54        let mut module = get_mod();
55        let Module {
56            bintable,
57            user_data,
58            ..
59        } = &mut *module;
60        let bin = bintable.get_mut(name).expect("Failed to find binary name");
61        match bin(
62            &mut **user_data,
63            name.to_str().expect("Failed to parse binary name"),
64            &args,
65            opts,
66        ) {
67            Ok(()) => 0,
68            Err(e) => {
69                let msg = to_cstr(e.to_string());
70                log::error_named(name, msg);
71                1
72            }
73        }
74    })
75    .unwrap_or(65)
76}
77
78pub fn set_mod(mut module: Module, name: &'static str) {
79    for x in module.features.get_binaries() {
80        x.handlerfunc = Some(builtin_callback)
81    }
82    module.name = Some(name);
83    *MODULE.module.lock() = Some(module);
84}
85
86fn drop_mod() {
87    if !panicked() {
88        MODULE.module.lock().take();
89    }
90}
91
92fn get_mod() -> parking_lot::MappedMutexGuard<'static, Module> {
93    parking_lot::MutexGuard::map(MODULE.module.lock(), |opt| {
94        opt.as_mut().expect("No module set")
95    })
96}
97
98fn panicked() -> bool {
99    MODULE.panicked.load(std::sync::atomic::Ordering::Acquire)
100}
101
102pub fn handle_maybe_error<E>(error: MaybeError<E>) -> i32
103where
104    E: std::fmt::Display,
105{
106    match error {
107        Ok(()) => 0,
108        Err(e) => {
109            let name = get_mod().name.unwrap();
110            crate::error!("{:?}: {}", name, e);
111            1
112        }
113    }
114}
115
116pub fn handle_panic<F, R>(cb: F) -> Option<R>
117where
118    F: FnOnce() -> R + std::panic::UnwindSafe,
119{
120    let res = std::panic::catch_unwind(|| cb());
121    match res {
122        Ok(ret) => Some(ret),
123        Err(err) => {
124            let name = get_mod().name.unwrap();
125            MODULE
126                .panicked
127                .store(true, std::sync::atomic::Ordering::Release);
128            if let Some(msg) = err.downcast_ref::<&str>() {
129                crate::error!("{:?} Panic: {}", name, msg);
130            } else if let Some(msg) = err.downcast_ref::<String>() {
131                crate::error!("{:?} Panic: {}", name, msg);
132            } else {
133                crate::error!("{:?} Panic: No additional information", name);
134            }
135            None
136        }
137    }
138}
139
140pub use paste;
141
142pub mod ffi {
143    pub use super::zsys::Module;
144}
145
146#[macro_export]
147/// Exports a `setup` function to be called when the module needs to be set up.
148/// You need to specify your module's loadable name
149macro_rules! export_module {
150    ($module_name:ident, $setupfn:ident) => {
151        #[doc(hidden)]
152        static MOD_NAME: &'static str = stringify!($module_name);
153
154        #[no_mangle]
155        #[doc(hidden)]
156        extern "C" fn setup_(_: $crate::export_module::ffi::Module) -> i32 {
157            $crate::export_module::handle_panic(|| {
158                let res = $setupfn().map(|module|
159                    $crate::export_module::set_mod(module, MOD_NAME)
160                );
161                $crate::export_module::handle_maybe_error(res)
162            })
163            .unwrap_or(65)
164        }
165
166        mod _zsh_private_glue {
167            use ::std::ffi::{ c_char, c_int };
168            $crate::export_module!(@fn boot_(module: $crate::export_module::ffi::Module));
169            $crate::export_module!(@fn features_(module: $crate::export_module::ffi::Module, features_ptr: *mut *mut *mut c_char));
170            $crate::export_module!(@fn enables_(module: $crate::export_module::ffi::Module, enables_ptr: *mut *mut c_int));
171            $crate::export_module!(@fn cleanup_(module: $crate::export_module::ffi::Module));
172            $crate::export_module!(@fn finish_(module: $crate::export_module::ffi::Module) );
173        }
174    };
175    (@fn $name:ident ($($arg:ident : $type:ty),*)) => {
176        #[no_mangle]
177        #[doc(hidden)]
178        extern "C" fn $name($($arg: $type),*) -> i32 {
179            $crate::export_module::$name($($arg),*)
180        }
181    }
182}
183
184macro_rules! mod_fn {
185    (fn $name:ident($mod:ident $(,$arg:ident : $type:ty)*) try $block:expr) => {
186        mod_fn!(
187            fn $name($mod $(,$arg : $type)*) {
188                handle_maybe_error($block)
189            }
190        );
191    };
192    (fn $name:ident($mod:ident $(,$arg:ident : $type:ty)*) $block:expr) => {
193        pub fn $name($mod: $crate::zsys::Module $(,$arg: $type)*) -> i32 {
194            handle_panic(|| {
195                $block
196            }).unwrap_or(65)
197        }
198    };
199}
200
201mod_fn!(
202    fn boot_(_mod) try {
203        // zsys::addwrapper()
204        Ok::<_, std::convert::Infallible>(())
205    }
206);
207
208mod_fn!(
209    fn features_(mod_, features_ptr: *mut *mut *mut c_char) {
210        let mut module = get_mod();
211        unsafe { *features_ptr = zsys::featuresarray(mod_, &mut *module.features) };
212        0
213    }
214);
215
216mod_fn!(
217    fn enables_(mod_, enables_ptr: *mut *mut c_int) {
218        let mut module = get_mod();
219        unsafe {
220            zsys::handlefeatures(mod_, &mut *module.features, enables_ptr)
221        }
222    }
223);
224
225// Called when cleaning the module up.
226mod_fn!(
227    fn cleanup_(_mod) {
228        let mut module = get_mod();
229        unsafe {
230            zsys::setfeatureenables(_mod, &mut *module.features, std::ptr::null_mut())
231        }
232    }
233);
234
235// Called after cleanup and when module fails to load.
236mod_fn!(
237    fn finish_(_mod) try {
238        drop_mod();
239        Ok::<(), std::convert::Infallible>(())
240    }
241);