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, CompFn, 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 compfn_registry() -> &'static Mutex<HashMap<String, (CompFn, String)>> {
98 static CR: OnceLock<Mutex<HashMap<String, (CompFn, String)>>> = OnceLock::new();
99 CR.get_or_init(|| Mutex::new(HashMap::new()))
100}
101
102fn compfn_staging() -> &'static Mutex<Vec<(String, CompFn)>> {
105 static CS: OnceLock<Mutex<Vec<(String, CompFn)>>> = OnceLock::new();
106 CS.get_or_init(|| Mutex::new(Vec::new()))
107}
108
109fn pending_completions() -> &'static Mutex<Vec<(String, String, String)>> {
117 static PC: OnceLock<Mutex<Vec<(String, String, String)>>> = OnceLock::new();
118 PC.get_or_init(|| Mutex::new(Vec::new()))
119}
120
121fn installed_completions() -> &'static Mutex<HashMap<String, String>> {
124 static IC: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
125 IC.get_or_init(|| Mutex::new(HashMap::new()))
126}
127
128extern "C" fn host_register_builtin(
134 _host: *const HostApi,
135 name: *const c_char,
136 handler: BuiltinFn,
137) -> c_int {
138 if name.is_null() {
139 return 1;
140 }
141 let name = unsafe { CStr::from_ptr(name) }
142 .to_string_lossy()
143 .into_owned();
144 staging().lock().unwrap().push((name, handler));
145 0
146}
147
148extern "C" fn host_print(_host: *const HostApi, text: *const c_char) {
149 if text.is_null() {
150 return;
151 }
152 let s = unsafe { CStr::from_ptr(text) }
153 .to_string_lossy()
154 .into_owned();
155 use std::io::Write as _;
156 let mut out = std::io::stdout();
157 let _ = out.write_all(s.as_bytes());
158 let _ = out.flush();
159}
160
161extern "C" fn host_eval(_host: *const HostApi, code: *const c_char) -> c_int {
162 if code.is_null() {
163 return 1;
164 }
165 let code = unsafe { CStr::from_ptr(code) }
166 .to_string_lossy()
167 .into_owned();
168 crate::fusevm_bridge::try_with_executor(|exec| exec.execute_script(&code))
172 .map(|r| r.unwrap_or(1))
173 .unwrap_or(1)
174}
175
176extern "C" fn host_getvar(_host: *const HostApi, name: *const c_char) -> *mut c_char {
177 if name.is_null() {
178 return std::ptr::null_mut();
179 }
180 let name = unsafe { CStr::from_ptr(name) }
181 .to_string_lossy()
182 .into_owned();
183 match crate::ported::params::getsparam(&name) {
184 Some(v) => match CString::new(v) {
185 Ok(c) => c.into_raw(),
186 Err(_) => std::ptr::null_mut(),
187 },
188 None => std::ptr::null_mut(),
189 }
190}
191
192extern "C" fn host_setvar(
193 _host: *const HostApi,
194 name: *const c_char,
195 value: *const c_char,
196) -> c_int {
197 if name.is_null() || value.is_null() {
198 return 1;
199 }
200 let name = unsafe { CStr::from_ptr(name) }
201 .to_string_lossy()
202 .into_owned();
203 let value = unsafe { CStr::from_ptr(value) }
204 .to_string_lossy()
205 .into_owned();
206 crate::ported::params::setsparam(&name, &value);
207 0
208}
209
210extern "C" fn host_free_cstring(_host: *const HostApi, s: *mut c_char) {
211 if !s.is_null() {
212 unsafe { drop(CString::from_raw(s)) };
214 }
215}
216
217extern "C" fn host_register_completion(
218 _host: *const HostApi,
219 cmd: *const c_char,
220 generator: *const c_char,
221) -> c_int {
222 if cmd.is_null() || generator.is_null() {
223 return 1;
224 }
225 let cmd = unsafe { CStr::from_ptr(cmd) }
226 .to_string_lossy()
227 .into_owned();
228 let generator = unsafe { CStr::from_ptr(generator) }
229 .to_string_lossy()
230 .into_owned();
231 pending_completions()
233 .lock()
234 .unwrap()
235 .push((cmd, generator, String::new()));
236 0
237}
238
239extern "C" fn host_getfunction(_host: *const HostApi, name: *const c_char) -> *mut c_char {
240 if name.is_null() {
241 return std::ptr::null_mut();
242 }
243 let name = unsafe { CStr::from_ptr(name) }
244 .to_string_lossy()
245 .into_owned();
246 match crate::ported::modules::parameter::getpmfunction(std::ptr::null_mut(), &name) {
249 Some(pm) if (pm.node.flags & crate::ported::zsh_h::PM_UNSET as i32) == 0 => {
250 match pm.u_str.and_then(|s| CString::new(s).ok()) {
251 Some(c) => c.into_raw(),
252 None => std::ptr::null_mut(),
253 }
254 }
255 _ => std::ptr::null_mut(),
256 }
257}
258
259extern "C" fn host_addfunction(
260 _host: *const HostApi,
261 name: *const c_char,
262 body: *const c_char,
263) -> c_int {
264 if name.is_null() || body.is_null() {
265 return 1;
266 }
267 let name = unsafe { CStr::from_ptr(name) }
268 .to_string_lossy()
269 .into_owned();
270 let body = unsafe { CStr::from_ptr(body) }
271 .to_string_lossy()
272 .into_owned();
273 if name.is_empty() {
274 return 1;
275 }
276 crate::ported::modules::parameter::setfunction(&name, body, 0);
279 0
281}
282
283extern "C" fn host_register_compfn(
284 _host: *const HostApi,
285 name: *const c_char,
286 handler: CompFn,
287) -> c_int {
288 if name.is_null() {
289 return 1;
290 }
291 let name = unsafe { CStr::from_ptr(name) }
292 .to_string_lossy()
293 .into_owned();
294 if name.is_empty() {
295 return 1;
296 }
297 compfn_staging().lock().unwrap().push((name, handler));
298 0
299}
300
301extern "C" fn host_comp_dispatch(
302 _host: *const HostApi,
303 name: *const c_char,
304 argc: usize,
305 argv: *const *const c_char,
306) -> c_int {
307 if name.is_null() {
308 return 1;
309 }
310 let name = unsafe { CStr::from_ptr(name) }
311 .to_string_lossy()
312 .into_owned();
313 let mut args: Vec<String> = Vec::with_capacity(argc);
316 if !argv.is_null() {
317 for i in 0..argc {
318 let p = unsafe { *argv.add(i) };
319 if p.is_null() {
320 break;
321 }
322 args.push(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned());
323 }
324 }
325 let call_args: &[String] = if args.is_empty() { &[] } else { &args[1..] };
329 crate::ported::exec::dispatch_function_call(&name, call_args).unwrap_or(1)
330}
331
332extern "C" fn host_empty_command_hash(_host: *const HostApi) {
333 crate::ported::hashtable::emptycmdnamtable();
334}
335
336fn host_api() -> *const HostApi {
340 static API: OnceLock<usize> = OnceLock::new();
341 let addr = API.get_or_init(|| {
342 let boxed = Box::new(HostApi {
343 abi_version: ABI_VERSION,
344 ctx: std::ptr::null_mut(),
345 register_builtin: host_register_builtin,
346 print: host_print,
347 eval: host_eval,
348 getvar: host_getvar,
349 setvar: host_setvar,
350 free_cstring: host_free_cstring,
351 register_completion: host_register_completion,
352 getfunction: host_getfunction,
353 addfunction: host_addfunction,
354 register_compfn: host_register_compfn,
355 comp_dispatch: host_comp_dispatch,
356 empty_command_hash: host_empty_command_hash,
357 });
358 Box::into_raw(boxed) as usize
359 });
360 *addr as *const HostApi
361}
362
363pub fn load(path: &str) -> Result<String, String> {
371 let _guard = load_lock().lock().unwrap();
372
373 let expanded = expand_tilde(path);
377 let lib = unsafe { libloading::Library::new(&expanded) }
378 .map_err(|e| format!("cannot load `{}`: {}", path, e))?;
379
380 let init: libloading::Symbol<InitFn> = unsafe {
382 lib.get(INIT_SYMBOL).map_err(|_| {
383 format!(
384 "`{}`: not a zshrs plugin (no {})",
385 path,
386 String::from_utf8_lossy(&INIT_SYMBOL[..INIT_SYMBOL.len() - 1])
387 )
388 })?
389 };
390
391 staging().lock().unwrap().clear();
395 compfn_staging().lock().unwrap().clear();
396 let pc_start = pending_completions().lock().unwrap().len();
397 let info_ptr: *const PluginInfo = init(host_api());
398 if info_ptr.is_null() {
399 staging().lock().unwrap().clear();
400 compfn_staging().lock().unwrap().clear();
401 pending_completions().lock().unwrap().truncate(pc_start);
402 return Err(format!(
403 "`{}`: plugin init failed (ABI mismatch or error)",
404 path
405 ));
406 }
407 let info = unsafe { &*info_ptr };
408 if info.abi_version != ABI_VERSION {
409 staging().lock().unwrap().clear();
410 compfn_staging().lock().unwrap().clear();
411 pending_completions().lock().unwrap().truncate(pc_start);
412 return Err(format!(
413 "`{}`: ABI version {} != host {}",
414 path, info.abi_version, ABI_VERSION
415 ));
416 }
417 let name = cstr_or(info.name, "unknown");
418 let version = cstr_or(info.version, "?");
419
420 if plugins().lock().unwrap().iter().any(|p| p.name == name) {
423 staging().lock().unwrap().clear();
424 compfn_staging().lock().unwrap().clear();
425 pending_completions().lock().unwrap().truncate(pc_start);
426 return Err(format!("plugin `{}` already loaded", name));
427 }
428
429 let staged: Vec<(String, BuiltinFn)> = std::mem::take(&mut *staging().lock().unwrap());
431 {
432 let mut reg = registry().lock().unwrap();
433 let mut own = ownership().lock().unwrap();
434 for (cmd, func) in staged {
435 reg.insert(cmd.clone(), BuiltinEntry { func, _pad: () });
436 own.insert(cmd, name.clone());
437 }
438 }
439
440 let staged_cf: Vec<(String, CompFn)> = std::mem::take(&mut *compfn_staging().lock().unwrap());
442 {
443 let mut cr = compfn_registry().lock().unwrap();
444 for (fname, func) in staged_cf {
445 cr.insert(fname, (func, name.clone()));
446 }
447 }
448
449 {
451 let mut pc = pending_completions().lock().unwrap();
452 for entry in pc.iter_mut().skip(pc_start) {
453 entry.2 = name.clone();
454 }
455 }
456
457 plugins().lock().unwrap().push(LoadedPlugin {
458 name: name.clone(),
459 version: version.clone(),
460 path: expanded,
461 _lib: lib,
462 });
463
464 tracing::info!(plugin = %name, version = %version, path, "loaded native plugin");
465 Ok(name)
466}
467
468pub fn flush_pending_completions() {
479 let pending: Vec<(String, String, String)> = {
480 let mut pc = pending_completions().lock().unwrap();
481 if pc.is_empty() {
482 return;
483 }
484 std::mem::take(&mut *pc)
485 };
486 for (cmd, generator, owner) in pending {
487 let glue = format!(
490 "_zshrs_plug_{cmd}() {{ \
491 local -a _zp_m; \
492 _zp_m=(\"${{(@f)$({gen} $CURRENT $words)}}\"); \
493 compadd -- $_zp_m; \
494 }}; \
495 (( ${{+functions[compdef]}} )) && compdef _zshrs_plug_{cmd} {cmd} 2>/dev/null; :",
496 cmd = cmd,
497 gen = generator,
498 );
499 let _ = crate::ported::exec::execute_script(&glue);
506 installed_completions().lock().unwrap().insert(cmd, owner);
507 }
508}
509
510pub fn unload(name: &str) -> Result<(), String> {
513 let _guard = load_lock().lock().unwrap();
514
515 let present = plugins().lock().unwrap().iter().any(|p| p.name == name);
516 if !present {
517 return Err(format!("plugin `{}` not loaded", name));
518 }
519
520 {
522 let mut own = ownership().lock().unwrap();
523 let mut reg = registry().lock().unwrap();
524 let owned: Vec<String> = own
525 .iter()
526 .filter(|(_, o)| o.as_str() == name)
527 .map(|(c, _)| c.clone())
528 .collect();
529 for cmd in owned {
530 reg.remove(&cmd);
531 own.remove(&cmd);
532 }
533 }
534
535 compfn_registry()
538 .lock()
539 .unwrap()
540 .retain(|_, (_, o)| o.as_str() != name);
541
542 pending_completions()
549 .lock()
550 .unwrap()
551 .retain(|(_, _, o)| o != name);
552 installed_completions()
553 .lock()
554 .unwrap()
555 .retain(|_, o| o != name);
556
557 let mut ps = plugins().lock().unwrap();
559 if let Some(pos) = ps.iter().position(|p| p.name == name) {
560 let p = ps.remove(pos);
561 tracing::info!(plugin = %name, "unloaded native plugin");
562 drop(p); }
564 Ok(())
565}
566
567pub fn compfn_override(name: &str) -> Option<CompFn> {
573 compfn_registry().lock().unwrap().get(name).map(|(f, _)| *f)
574}
575
576pub fn dispatch_compfn(name: &str, args: &[String]) -> Option<i32> {
582 let func = compfn_override(name)?;
583 let mut owned: Vec<CString> = Vec::with_capacity(args.len() + 1);
586 owned.push(CString::new(name).ok()?);
587 for a in args {
588 owned.push(
589 CString::new(a.as_str())
590 .unwrap_or_else(|_| CString::new(a.replace('\0', "")).unwrap_or_default()),
591 );
592 }
593 let ptrs: Vec<*const c_char> = owned.iter().map(|c| c.as_ptr()).collect();
594 let rc = func(host_api(), ptrs.len(), ptrs.as_ptr());
595 Some(rc as i32)
596}
597
598pub fn dispatch(cmd: &str, args: &[String]) -> Option<i32> {
602 let entry = { registry().lock().unwrap().get(cmd).copied() }?;
603
604 let mut owned: Vec<CString> = Vec::with_capacity(args.len() + 1);
607 owned.push(CString::new(cmd).ok()?);
608 for a in args {
609 owned.push(
610 CString::new(a.as_str())
611 .unwrap_or_else(|_| CString::new(a.replace('\0', "")).unwrap_or_default()),
612 );
613 }
614 let ptrs: Vec<*const c_char> = owned.iter().map(|c| c.as_ptr()).collect();
615
616 let rc = (entry.func)(host_api(), ptrs.len(), ptrs.as_ptr());
617 Some(rc as i32)
619}
620
621pub fn list() -> Vec<(String, String, String)> {
624 let mut v: Vec<(String, String, String)> = plugins()
625 .lock()
626 .unwrap()
627 .iter()
628 .map(|p| (p.name.clone(), p.version.clone(), p.path.clone()))
629 .collect();
630 v.sort_by(|a, b| a.0.cmp(&b.0));
631 v
632}
633
634pub fn is_plugin_command(name: &str) -> bool {
637 registry().lock().unwrap().contains_key(name)
638}
639
640pub fn zmodload_rust_cmd(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options) -> i32 {
648 use crate::ported::utils::zwarnnam;
649 use crate::ported::zsh_h::OPT_ISSET;
650
651 if OPT_ISSET(ops, b'u') {
653 if args.is_empty() {
654 zwarnnam(nam, "what do you want to unload?");
655 return 1;
656 }
657 let mut ret = 0;
658 for name in args {
659 if let Err(e) = unload(name) {
660 zwarnnam(nam, &e);
661 ret = 1;
662 }
663 }
664 return ret;
665 }
666 if args.is_empty() {
668 for (name, version, path) in list() {
669 println!("{} {} {}", name, version, path);
670 }
671 return 0;
672 }
673 let mut ret = 0;
675 for path in args {
676 if let Err(e) = load(path) {
677 zwarnnam(nam, &e);
678 ret = 1;
679 }
680 }
681 ret
682}
683
684fn cstr_or(p: *const c_char, dflt: &str) -> String {
685 if p.is_null() {
686 dflt.to_string()
687 } else {
688 unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
689 }
690}
691
692fn expand_tilde(path: &str) -> String {
693 if let Some(rest) = path.strip_prefix("~/") {
694 if let Some(home) = dirs::home_dir() {
695 return home.join(rest).to_string_lossy().into_owned();
696 }
697 }
698 path.to_string()
699}