1#![allow(unused_imports)]
32
33use std::collections::HashMap;
34use std::ffi::{CStr, CString};
35use std::os::raw::{c_char, c_int};
36use std::sync::{Mutex, OnceLock};
37
38use znative::{BuiltinFn, HostApi, InitFn, PluginInfo, ABI_VERSION, INIT_SYMBOL};
39
40struct LoadedPlugin {
44 name: String,
45 version: String,
46 path: String,
47 _lib: libloading::Library,
49}
50
51#[derive(Clone, Copy)]
53struct BuiltinEntry {
54 func: BuiltinFn,
55 _pad: (),
58}
59
60fn plugins() -> &'static Mutex<Vec<LoadedPlugin>> {
61 static P: OnceLock<Mutex<Vec<LoadedPlugin>>> = OnceLock::new();
62 P.get_or_init(|| Mutex::new(Vec::new()))
63}
64
65fn registry() -> &'static Mutex<HashMap<String, BuiltinEntry>> {
67 static R: OnceLock<Mutex<HashMap<String, BuiltinEntry>>> = OnceLock::new();
68 R.get_or_init(|| Mutex::new(HashMap::new()))
69}
70
71fn staging() -> &'static Mutex<Vec<(String, BuiltinFn)>> {
76 static S: OnceLock<Mutex<Vec<(String, BuiltinFn)>>> = OnceLock::new();
77 S.get_or_init(|| Mutex::new(Vec::new()))
78}
79
80fn load_lock() -> &'static Mutex<()> {
82 static L: OnceLock<Mutex<()>> = OnceLock::new();
83 L.get_or_init(|| Mutex::new(()))
84}
85
86fn ownership() -> &'static Mutex<HashMap<String, String>> {
89 static O: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
90 O.get_or_init(|| Mutex::new(HashMap::new()))
91}
92
93fn pending_completions() -> &'static Mutex<Vec<(String, String, String)>> {
101 static PC: OnceLock<Mutex<Vec<(String, String, String)>>> = OnceLock::new();
102 PC.get_or_init(|| Mutex::new(Vec::new()))
103}
104
105fn installed_completions() -> &'static Mutex<HashMap<String, String>> {
108 static IC: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
109 IC.get_or_init(|| Mutex::new(HashMap::new()))
110}
111
112extern "C" fn host_register_builtin(
118 _host: *const HostApi,
119 name: *const c_char,
120 handler: BuiltinFn,
121) -> c_int {
122 if name.is_null() {
123 return 1;
124 }
125 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
126 staging().lock().unwrap().push((name, handler));
127 0
128}
129
130extern "C" fn host_print(_host: *const HostApi, text: *const c_char) {
131 if text.is_null() {
132 return;
133 }
134 let s = unsafe { CStr::from_ptr(text) }.to_string_lossy().into_owned();
135 use std::io::Write as _;
136 let mut out = std::io::stdout();
137 let _ = out.write_all(s.as_bytes());
138 let _ = out.flush();
139}
140
141extern "C" fn host_eval(_host: *const HostApi, code: *const c_char) -> c_int {
142 if code.is_null() {
143 return 1;
144 }
145 let code = unsafe { CStr::from_ptr(code) }.to_string_lossy().into_owned();
146 crate::fusevm_bridge::try_with_executor(|exec| exec.execute_script(&code))
150 .map(|r| r.unwrap_or(1))
151 .unwrap_or(1)
152}
153
154extern "C" fn host_getvar(_host: *const HostApi, name: *const c_char) -> *mut c_char {
155 if name.is_null() {
156 return std::ptr::null_mut();
157 }
158 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
159 match crate::ported::params::getsparam(&name) {
160 Some(v) => match CString::new(v) {
161 Ok(c) => c.into_raw(),
162 Err(_) => std::ptr::null_mut(),
163 },
164 None => std::ptr::null_mut(),
165 }
166}
167
168extern "C" fn host_setvar(
169 _host: *const HostApi,
170 name: *const c_char,
171 value: *const c_char,
172) -> c_int {
173 if name.is_null() || value.is_null() {
174 return 1;
175 }
176 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
177 let value = unsafe { CStr::from_ptr(value) }.to_string_lossy().into_owned();
178 crate::ported::params::setsparam(&name, &value);
179 0
180}
181
182extern "C" fn host_free_cstring(_host: *const HostApi, s: *mut c_char) {
183 if !s.is_null() {
184 unsafe { drop(CString::from_raw(s)) };
186 }
187}
188
189extern "C" fn host_register_completion(
190 _host: *const HostApi,
191 cmd: *const c_char,
192 generator: *const c_char,
193) -> c_int {
194 if cmd.is_null() || generator.is_null() {
195 return 1;
196 }
197 let cmd = unsafe { CStr::from_ptr(cmd) }.to_string_lossy().into_owned();
198 let generator = unsafe { CStr::from_ptr(generator) }
199 .to_string_lossy()
200 .into_owned();
201 pending_completions()
203 .lock()
204 .unwrap()
205 .push((cmd, generator, String::new()));
206 0
207}
208
209extern "C" fn host_getfunction(_host: *const HostApi, name: *const c_char) -> *mut c_char {
210 if name.is_null() {
211 return std::ptr::null_mut();
212 }
213 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
214 match crate::ported::modules::parameter::getpmfunction(std::ptr::null_mut(), &name) {
217 Some(pm) if (pm.node.flags & crate::ported::zsh_h::PM_UNSET as i32) == 0 => {
218 match pm.u_str.and_then(|s| CString::new(s).ok()) {
219 Some(c) => c.into_raw(),
220 None => std::ptr::null_mut(),
221 }
222 }
223 _ => std::ptr::null_mut(),
224 }
225}
226
227extern "C" fn host_addfunction(
228 _host: *const HostApi,
229 name: *const c_char,
230 body: *const c_char,
231) -> c_int {
232 if name.is_null() || body.is_null() {
233 return 1;
234 }
235 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
236 let body = unsafe { CStr::from_ptr(body) }.to_string_lossy().into_owned();
237 if name.is_empty() {
238 return 1;
239 }
240 crate::ported::modules::parameter::setfunction(&name, body, 0);
243 0
245}
246
247fn host_api() -> *const HostApi {
251 static API: OnceLock<usize> = OnceLock::new();
252 let addr = API.get_or_init(|| {
253 let boxed = Box::new(HostApi {
254 abi_version: ABI_VERSION,
255 ctx: std::ptr::null_mut(),
256 register_builtin: host_register_builtin,
257 print: host_print,
258 eval: host_eval,
259 getvar: host_getvar,
260 setvar: host_setvar,
261 free_cstring: host_free_cstring,
262 register_completion: host_register_completion,
263 getfunction: host_getfunction,
264 addfunction: host_addfunction,
265 });
266 Box::into_raw(boxed) as usize
267 });
268 *addr as *const HostApi
269}
270
271pub fn load(path: &str) -> Result<String, String> {
279 let _guard = load_lock().lock().unwrap();
280
281 let expanded = expand_tilde(path);
285 let lib = unsafe { libloading::Library::new(&expanded) }
286 .map_err(|e| format!("cannot load `{}`: {}", path, e))?;
287
288 let init: libloading::Symbol<InitFn> = unsafe {
290 lib.get(INIT_SYMBOL)
291 .map_err(|_| format!("`{}`: not a zshrs plugin (no {})", path,
292 String::from_utf8_lossy(&INIT_SYMBOL[..INIT_SYMBOL.len() - 1])))?
293 };
294
295 staging().lock().unwrap().clear();
299 let pc_start = pending_completions().lock().unwrap().len();
300 let info_ptr: *const PluginInfo = init(host_api());
301 if info_ptr.is_null() {
302 staging().lock().unwrap().clear();
303 pending_completions().lock().unwrap().truncate(pc_start);
304 return Err(format!("`{}`: plugin init failed (ABI mismatch or error)", path));
305 }
306 let info = unsafe { &*info_ptr };
307 if info.abi_version != ABI_VERSION {
308 staging().lock().unwrap().clear();
309 pending_completions().lock().unwrap().truncate(pc_start);
310 return Err(format!(
311 "`{}`: ABI version {} != host {}",
312 path, info.abi_version, ABI_VERSION
313 ));
314 }
315 let name = cstr_or(info.name, "unknown");
316 let version = cstr_or(info.version, "?");
317
318 if plugins().lock().unwrap().iter().any(|p| p.name == name) {
321 staging().lock().unwrap().clear();
322 pending_completions().lock().unwrap().truncate(pc_start);
323 return Err(format!("plugin `{}` already loaded", name));
324 }
325
326 let staged: Vec<(String, BuiltinFn)> = std::mem::take(&mut *staging().lock().unwrap());
328 {
329 let mut reg = registry().lock().unwrap();
330 let mut own = ownership().lock().unwrap();
331 for (cmd, func) in staged {
332 reg.insert(cmd.clone(), BuiltinEntry { func, _pad: () });
333 own.insert(cmd, name.clone());
334 }
335 }
336
337 {
339 let mut pc = pending_completions().lock().unwrap();
340 for entry in pc.iter_mut().skip(pc_start) {
341 entry.2 = name.clone();
342 }
343 }
344
345 plugins().lock().unwrap().push(LoadedPlugin {
346 name: name.clone(),
347 version: version.clone(),
348 path: expanded,
349 _lib: lib,
350 });
351
352 tracing::info!(plugin = %name, version = %version, path, "loaded native plugin");
353 Ok(name)
354}
355
356pub fn flush_pending_completions() {
367 let pending: Vec<(String, String, String)> = {
368 let mut pc = pending_completions().lock().unwrap();
369 if pc.is_empty() {
370 return;
371 }
372 std::mem::take(&mut *pc)
373 };
374 for (cmd, generator, owner) in pending {
375 let glue = format!(
378 "_zshrs_plug_{cmd}() {{ \
379 local -a _zp_m; \
380 _zp_m=(\"${{(@f)$({gen} $CURRENT $words)}}\"); \
381 compadd -- $_zp_m; \
382 }}; \
383 (( ${{+functions[compdef]}} )) && compdef _zshrs_plug_{cmd} {cmd} 2>/dev/null; :",
384 cmd = cmd,
385 gen = generator,
386 );
387 let _ = crate::ported::exec::execute_script(&glue);
394 installed_completions()
395 .lock()
396 .unwrap()
397 .insert(cmd, owner);
398 }
399}
400
401pub fn unload(name: &str) -> Result<(), String> {
404 let _guard = load_lock().lock().unwrap();
405
406 let present = plugins().lock().unwrap().iter().any(|p| p.name == name);
407 if !present {
408 return Err(format!("plugin `{}` not loaded", name));
409 }
410
411 {
413 let mut own = ownership().lock().unwrap();
414 let mut reg = registry().lock().unwrap();
415 let owned: Vec<String> = own
416 .iter()
417 .filter(|(_, o)| o.as_str() == name)
418 .map(|(c, _)| c.clone())
419 .collect();
420 for cmd in owned {
421 reg.remove(&cmd);
422 own.remove(&cmd);
423 }
424 }
425
426 pending_completions()
433 .lock()
434 .unwrap()
435 .retain(|(_, _, o)| o != name);
436 installed_completions()
437 .lock()
438 .unwrap()
439 .retain(|_, o| o != name);
440
441 let mut ps = plugins().lock().unwrap();
443 if let Some(pos) = ps.iter().position(|p| p.name == name) {
444 let p = ps.remove(pos);
445 tracing::info!(plugin = %name, "unloaded native plugin");
446 drop(p); }
448 Ok(())
449}
450
451pub fn dispatch(cmd: &str, args: &[String]) -> Option<i32> {
455 let entry = { registry().lock().unwrap().get(cmd).copied() }?;
456
457 let mut owned: Vec<CString> = Vec::with_capacity(args.len() + 1);
460 owned.push(CString::new(cmd).ok()?);
461 for a in args {
462 owned.push(CString::new(a.as_str()).unwrap_or_else(|_| {
463 CString::new(a.replace('\0', "")).unwrap_or_default()
464 }));
465 }
466 let ptrs: Vec<*const c_char> = owned.iter().map(|c| c.as_ptr()).collect();
467
468 let rc = (entry.func)(host_api(), ptrs.len(), ptrs.as_ptr());
469 Some(rc as i32)
471}
472
473pub fn list() -> Vec<(String, String, String)> {
476 let mut v: Vec<(String, String, String)> = plugins()
477 .lock()
478 .unwrap()
479 .iter()
480 .map(|p| (p.name.clone(), p.version.clone(), p.path.clone()))
481 .collect();
482 v.sort_by(|a, b| a.0.cmp(&b.0));
483 v
484}
485
486pub fn is_plugin_command(name: &str) -> bool {
489 registry().lock().unwrap().contains_key(name)
490}
491
492pub fn zmodload_rust_cmd(
500 nam: &str,
501 args: &[String],
502 ops: &crate::ported::zsh_h::options,
503) -> i32 {
504 use crate::ported::utils::zwarnnam;
505 use crate::ported::zsh_h::OPT_ISSET;
506
507 if OPT_ISSET(ops, b'u') {
509 if args.is_empty() {
510 zwarnnam(nam, "what do you want to unload?");
511 return 1;
512 }
513 let mut ret = 0;
514 for name in args {
515 if let Err(e) = unload(name) {
516 zwarnnam(nam, &e);
517 ret = 1;
518 }
519 }
520 return ret;
521 }
522 if args.is_empty() {
524 for (name, version, path) in list() {
525 println!("{} {} {}", name, version, path);
526 }
527 return 0;
528 }
529 let mut ret = 0;
531 for path in args {
532 if let Err(e) = load(path) {
533 zwarnnam(nam, &e);
534 ret = 1;
535 }
536 }
537 ret
538}
539
540fn cstr_or(p: *const c_char, dflt: &str) -> String {
541 if p.is_null() {
542 dflt.to_string()
543 } else {
544 unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
545 }
546}
547
548fn expand_tilde(path: &str) -> String {
549 if let Some(rest) = path.strip_prefix("~/") {
550 if let Some(home) = dirs::home_dir() {
551 return home.join(rest).to_string_lossy().into_owned();
552 }
553 }
554 path.to_string()
555}