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) }.to_string_lossy().into_owned();
142 staging().lock().unwrap().push((name, handler));
143 0
144}
145
146extern "C" fn host_print(_host: *const HostApi, text: *const c_char) {
147 if text.is_null() {
148 return;
149 }
150 let s = unsafe { CStr::from_ptr(text) }.to_string_lossy().into_owned();
151 use std::io::Write as _;
152 let mut out = std::io::stdout();
153 let _ = out.write_all(s.as_bytes());
154 let _ = out.flush();
155}
156
157extern "C" fn host_eval(_host: *const HostApi, code: *const c_char) -> c_int {
158 if code.is_null() {
159 return 1;
160 }
161 let code = unsafe { CStr::from_ptr(code) }.to_string_lossy().into_owned();
162 crate::fusevm_bridge::try_with_executor(|exec| exec.execute_script(&code))
166 .map(|r| r.unwrap_or(1))
167 .unwrap_or(1)
168}
169
170extern "C" fn host_getvar(_host: *const HostApi, name: *const c_char) -> *mut c_char {
171 if name.is_null() {
172 return std::ptr::null_mut();
173 }
174 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
175 match crate::ported::params::getsparam(&name) {
176 Some(v) => match CString::new(v) {
177 Ok(c) => c.into_raw(),
178 Err(_) => std::ptr::null_mut(),
179 },
180 None => std::ptr::null_mut(),
181 }
182}
183
184extern "C" fn host_setvar(
185 _host: *const HostApi,
186 name: *const c_char,
187 value: *const c_char,
188) -> c_int {
189 if name.is_null() || value.is_null() {
190 return 1;
191 }
192 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
193 let value = unsafe { CStr::from_ptr(value) }.to_string_lossy().into_owned();
194 crate::ported::params::setsparam(&name, &value);
195 0
196}
197
198extern "C" fn host_free_cstring(_host: *const HostApi, s: *mut c_char) {
199 if !s.is_null() {
200 unsafe { drop(CString::from_raw(s)) };
202 }
203}
204
205extern "C" fn host_register_completion(
206 _host: *const HostApi,
207 cmd: *const c_char,
208 generator: *const c_char,
209) -> c_int {
210 if cmd.is_null() || generator.is_null() {
211 return 1;
212 }
213 let cmd = unsafe { CStr::from_ptr(cmd) }.to_string_lossy().into_owned();
214 let generator = unsafe { CStr::from_ptr(generator) }
215 .to_string_lossy()
216 .into_owned();
217 pending_completions()
219 .lock()
220 .unwrap()
221 .push((cmd, generator, String::new()));
222 0
223}
224
225extern "C" fn host_getfunction(_host: *const HostApi, name: *const c_char) -> *mut c_char {
226 if name.is_null() {
227 return std::ptr::null_mut();
228 }
229 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
230 match crate::ported::modules::parameter::getpmfunction(std::ptr::null_mut(), &name) {
233 Some(pm) if (pm.node.flags & crate::ported::zsh_h::PM_UNSET as i32) == 0 => {
234 match pm.u_str.and_then(|s| CString::new(s).ok()) {
235 Some(c) => c.into_raw(),
236 None => std::ptr::null_mut(),
237 }
238 }
239 _ => std::ptr::null_mut(),
240 }
241}
242
243extern "C" fn host_addfunction(
244 _host: *const HostApi,
245 name: *const c_char,
246 body: *const c_char,
247) -> c_int {
248 if name.is_null() || body.is_null() {
249 return 1;
250 }
251 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
252 let body = unsafe { CStr::from_ptr(body) }.to_string_lossy().into_owned();
253 if name.is_empty() {
254 return 1;
255 }
256 crate::ported::modules::parameter::setfunction(&name, body, 0);
259 0
261}
262
263extern "C" fn host_register_compfn(
264 _host: *const HostApi,
265 name: *const c_char,
266 handler: CompFn,
267) -> c_int {
268 if name.is_null() {
269 return 1;
270 }
271 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
272 if name.is_empty() {
273 return 1;
274 }
275 compfn_staging().lock().unwrap().push((name, handler));
276 0
277}
278
279extern "C" fn host_comp_dispatch(
280 _host: *const HostApi,
281 name: *const c_char,
282 argc: usize,
283 argv: *const *const c_char,
284) -> c_int {
285 if name.is_null() {
286 return 1;
287 }
288 let name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
289 let mut args: Vec<String> = Vec::with_capacity(argc);
292 if !argv.is_null() {
293 for i in 0..argc {
294 let p = unsafe { *argv.add(i) };
295 if p.is_null() {
296 break;
297 }
298 args.push(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned());
299 }
300 }
301 let call_args: &[String] = if args.is_empty() { &[] } else { &args[1..] };
305 crate::ported::exec::dispatch_function_call(&name, call_args).unwrap_or(1)
306}
307
308extern "C" fn host_empty_command_hash(_host: *const HostApi) {
309 crate::ported::hashtable::emptycmdnamtable();
310}
311
312fn host_api() -> *const HostApi {
316 static API: OnceLock<usize> = OnceLock::new();
317 let addr = API.get_or_init(|| {
318 let boxed = Box::new(HostApi {
319 abi_version: ABI_VERSION,
320 ctx: std::ptr::null_mut(),
321 register_builtin: host_register_builtin,
322 print: host_print,
323 eval: host_eval,
324 getvar: host_getvar,
325 setvar: host_setvar,
326 free_cstring: host_free_cstring,
327 register_completion: host_register_completion,
328 getfunction: host_getfunction,
329 addfunction: host_addfunction,
330 register_compfn: host_register_compfn,
331 comp_dispatch: host_comp_dispatch,
332 empty_command_hash: host_empty_command_hash,
333 });
334 Box::into_raw(boxed) as usize
335 });
336 *addr as *const HostApi
337}
338
339pub fn load(path: &str) -> Result<String, String> {
347 let _guard = load_lock().lock().unwrap();
348
349 let expanded = expand_tilde(path);
353 let lib = unsafe { libloading::Library::new(&expanded) }
354 .map_err(|e| format!("cannot load `{}`: {}", path, e))?;
355
356 let init: libloading::Symbol<InitFn> = unsafe {
358 lib.get(INIT_SYMBOL)
359 .map_err(|_| format!("`{}`: not a zshrs plugin (no {})", path,
360 String::from_utf8_lossy(&INIT_SYMBOL[..INIT_SYMBOL.len() - 1])))?
361 };
362
363 staging().lock().unwrap().clear();
367 compfn_staging().lock().unwrap().clear();
368 let pc_start = pending_completions().lock().unwrap().len();
369 let info_ptr: *const PluginInfo = init(host_api());
370 if info_ptr.is_null() {
371 staging().lock().unwrap().clear();
372 compfn_staging().lock().unwrap().clear();
373 pending_completions().lock().unwrap().truncate(pc_start);
374 return Err(format!("`{}`: plugin init failed (ABI mismatch or error)", path));
375 }
376 let info = unsafe { &*info_ptr };
377 if info.abi_version != ABI_VERSION {
378 staging().lock().unwrap().clear();
379 compfn_staging().lock().unwrap().clear();
380 pending_completions().lock().unwrap().truncate(pc_start);
381 return Err(format!(
382 "`{}`: ABI version {} != host {}",
383 path, info.abi_version, ABI_VERSION
384 ));
385 }
386 let name = cstr_or(info.name, "unknown");
387 let version = cstr_or(info.version, "?");
388
389 if plugins().lock().unwrap().iter().any(|p| p.name == name) {
392 staging().lock().unwrap().clear();
393 compfn_staging().lock().unwrap().clear();
394 pending_completions().lock().unwrap().truncate(pc_start);
395 return Err(format!("plugin `{}` already loaded", name));
396 }
397
398 let staged: Vec<(String, BuiltinFn)> = std::mem::take(&mut *staging().lock().unwrap());
400 {
401 let mut reg = registry().lock().unwrap();
402 let mut own = ownership().lock().unwrap();
403 for (cmd, func) in staged {
404 reg.insert(cmd.clone(), BuiltinEntry { func, _pad: () });
405 own.insert(cmd, name.clone());
406 }
407 }
408
409 let staged_cf: Vec<(String, CompFn)> =
411 std::mem::take(&mut *compfn_staging().lock().unwrap());
412 {
413 let mut cr = compfn_registry().lock().unwrap();
414 for (fname, func) in staged_cf {
415 cr.insert(fname, (func, name.clone()));
416 }
417 }
418
419 {
421 let mut pc = pending_completions().lock().unwrap();
422 for entry in pc.iter_mut().skip(pc_start) {
423 entry.2 = name.clone();
424 }
425 }
426
427 plugins().lock().unwrap().push(LoadedPlugin {
428 name: name.clone(),
429 version: version.clone(),
430 path: expanded,
431 _lib: lib,
432 });
433
434 tracing::info!(plugin = %name, version = %version, path, "loaded native plugin");
435 Ok(name)
436}
437
438pub fn flush_pending_completions() {
449 let pending: Vec<(String, String, String)> = {
450 let mut pc = pending_completions().lock().unwrap();
451 if pc.is_empty() {
452 return;
453 }
454 std::mem::take(&mut *pc)
455 };
456 for (cmd, generator, owner) in pending {
457 let glue = format!(
460 "_zshrs_plug_{cmd}() {{ \
461 local -a _zp_m; \
462 _zp_m=(\"${{(@f)$({gen} $CURRENT $words)}}\"); \
463 compadd -- $_zp_m; \
464 }}; \
465 (( ${{+functions[compdef]}} )) && compdef _zshrs_plug_{cmd} {cmd} 2>/dev/null; :",
466 cmd = cmd,
467 gen = generator,
468 );
469 let _ = crate::ported::exec::execute_script(&glue);
476 installed_completions()
477 .lock()
478 .unwrap()
479 .insert(cmd, owner);
480 }
481}
482
483pub fn unload(name: &str) -> Result<(), String> {
486 let _guard = load_lock().lock().unwrap();
487
488 let present = plugins().lock().unwrap().iter().any(|p| p.name == name);
489 if !present {
490 return Err(format!("plugin `{}` not loaded", name));
491 }
492
493 {
495 let mut own = ownership().lock().unwrap();
496 let mut reg = registry().lock().unwrap();
497 let owned: Vec<String> = own
498 .iter()
499 .filter(|(_, o)| o.as_str() == name)
500 .map(|(c, _)| c.clone())
501 .collect();
502 for cmd in owned {
503 reg.remove(&cmd);
504 own.remove(&cmd);
505 }
506 }
507
508 compfn_registry()
511 .lock()
512 .unwrap()
513 .retain(|_, (_, o)| o.as_str() != name);
514
515 pending_completions()
522 .lock()
523 .unwrap()
524 .retain(|(_, _, o)| o != name);
525 installed_completions()
526 .lock()
527 .unwrap()
528 .retain(|_, o| o != name);
529
530 let mut ps = plugins().lock().unwrap();
532 if let Some(pos) = ps.iter().position(|p| p.name == name) {
533 let p = ps.remove(pos);
534 tracing::info!(plugin = %name, "unloaded native plugin");
535 drop(p); }
537 Ok(())
538}
539
540pub fn compfn_override(name: &str) -> Option<CompFn> {
546 compfn_registry().lock().unwrap().get(name).map(|(f, _)| *f)
547}
548
549pub fn dispatch_compfn(name: &str, args: &[String]) -> Option<i32> {
555 let func = compfn_override(name)?;
556 let mut owned: Vec<CString> = Vec::with_capacity(args.len() + 1);
559 owned.push(CString::new(name).ok()?);
560 for a in args {
561 owned.push(
562 CString::new(a.as_str())
563 .unwrap_or_else(|_| CString::new(a.replace('\0', "")).unwrap_or_default()),
564 );
565 }
566 let ptrs: Vec<*const c_char> = owned.iter().map(|c| c.as_ptr()).collect();
567 let rc = func(host_api(), ptrs.len(), ptrs.as_ptr());
568 Some(rc as i32)
569}
570
571pub fn dispatch(cmd: &str, args: &[String]) -> Option<i32> {
575 let entry = { registry().lock().unwrap().get(cmd).copied() }?;
576
577 let mut owned: Vec<CString> = Vec::with_capacity(args.len() + 1);
580 owned.push(CString::new(cmd).ok()?);
581 for a in args {
582 owned.push(CString::new(a.as_str()).unwrap_or_else(|_| {
583 CString::new(a.replace('\0', "")).unwrap_or_default()
584 }));
585 }
586 let ptrs: Vec<*const c_char> = owned.iter().map(|c| c.as_ptr()).collect();
587
588 let rc = (entry.func)(host_api(), ptrs.len(), ptrs.as_ptr());
589 Some(rc as i32)
591}
592
593pub fn list() -> Vec<(String, String, String)> {
596 let mut v: Vec<(String, String, String)> = plugins()
597 .lock()
598 .unwrap()
599 .iter()
600 .map(|p| (p.name.clone(), p.version.clone(), p.path.clone()))
601 .collect();
602 v.sort_by(|a, b| a.0.cmp(&b.0));
603 v
604}
605
606pub fn is_plugin_command(name: &str) -> bool {
609 registry().lock().unwrap().contains_key(name)
610}
611
612pub fn zmodload_rust_cmd(
620 nam: &str,
621 args: &[String],
622 ops: &crate::ported::zsh_h::options,
623) -> i32 {
624 use crate::ported::utils::zwarnnam;
625 use crate::ported::zsh_h::OPT_ISSET;
626
627 if OPT_ISSET(ops, b'u') {
629 if args.is_empty() {
630 zwarnnam(nam, "what do you want to unload?");
631 return 1;
632 }
633 let mut ret = 0;
634 for name in args {
635 if let Err(e) = unload(name) {
636 zwarnnam(nam, &e);
637 ret = 1;
638 }
639 }
640 return ret;
641 }
642 if args.is_empty() {
644 for (name, version, path) in list() {
645 println!("{} {} {}", name, version, path);
646 }
647 return 0;
648 }
649 let mut ret = 0;
651 for path in args {
652 if let Err(e) = load(path) {
653 zwarnnam(nam, &e);
654 ret = 1;
655 }
656 }
657 ret
658}
659
660fn cstr_or(p: *const c_char, dflt: &str) -> String {
661 if p.is_null() {
662 dflt.to_string()
663 } else {
664 unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
665 }
666}
667
668fn expand_tilde(path: &str) -> String {
669 if let Some(rest) = path.strip_prefix("~/") {
670 if let Some(home) = dirs::home_dir() {
671 return home.join(rest).to_string_lossy().into_owned();
672 }
673 }
674 path.to_string()
675}