pmpx_plugin/abi/
dispatch.rs1use std::path::PathBuf;
7
8use crate::{CommandSpec, Context, PackageManager, Verb};
9
10use super::marshal::{free_str, leak_bytes, os_to_bytes, read_os, read_str};
11use super::types::{PmpxCommand, PmpxStr, PMPX_ERR_INTERNAL, PMPX_ERR_INVALID_ARGS, PMPX_OK};
12
13pub unsafe fn write_command(out: *mut PmpxCommand, spec: CommandSpec) {
17 let program = leak_bytes(&os_to_bytes(&spec.program));
18
19 let args: Vec<PmpxStr> = spec
20 .args
21 .iter()
22 .map(|a| leak_bytes(&os_to_bytes(a)))
23 .collect();
24 let args_boxed: Box<[PmpxStr]> = args.into_boxed_slice();
25 let args_len = args_boxed.len();
26 let args_ptr = args_boxed.as_ptr();
27 std::mem::forget(args_boxed);
28
29 let cwd = match &spec.cwd {
30 Some(p) => leak_bytes(&os_to_bytes(p.as_os_str())),
31 None => PmpxStr::EMPTY,
32 };
33
34 unsafe {
35 *out = PmpxCommand {
36 program,
37 args: args_ptr,
38 args_len,
39 cwd,
40 };
41 }
42}
43
44pub unsafe fn free_command(c: *mut PmpxCommand) {
49 if c.is_null() {
50 return;
51 }
52 let cmd = unsafe { &*c };
53
54 unsafe { free_str(cmd.program) };
55 unsafe { free_str(cmd.cwd) };
56
57 if !cmd.args.is_null() && cmd.args_len > 0 {
58 let raw = std::ptr::slice_from_raw_parts_mut(cmd.args as *mut PmpxStr, cmd.args_len);
60 let args = unsafe { Box::from_raw(raw) };
61 for s in args.iter() {
62 unsafe { free_str(*s) };
63 }
64 }
65}
66
67#[allow(clippy::too_many_arguments)]
74pub unsafe fn dispatch_command(
75 plugin: &dyn PackageManager,
76 project_root: PmpxStr,
77 matched: *const PmpxStr,
78 matched_len: usize,
79 verb: u32,
80 args: *const PmpxStr,
81 args_len: usize,
82 out: *mut PmpxCommand,
83) -> u32 {
84 if out.is_null() {
85 return PMPX_ERR_INVALID_ARGS;
86 }
87
88 let Some(verb) = Verb::from_abi(verb) else {
89 return PMPX_ERR_INVALID_ARGS;
90 };
91
92 let project_root = PathBuf::from(unsafe { read_os(project_root) });
93
94 let mut matched_names = Vec::with_capacity(matched_len);
96 for i in 0..matched_len {
97 let raw = unsafe { *matched.add(i) };
98 match unsafe { read_str(raw) } {
99 Ok(s) => matched_names.push(s.to_string()),
100 Err(code) => return code,
101 }
102 }
103
104 let mut arg_list = Vec::with_capacity(args_len);
106 for i in 0..args_len {
107 let raw = unsafe { *args.add(i) };
108 arg_list.push(unsafe { read_os(raw) });
109 }
110
111 let ctx = Context {
112 project_root,
113 matched: matched_names,
114 };
115
116 match plugin.command(&ctx, verb, &arg_list) {
117 Ok(spec) => {
118 unsafe { write_command(out, spec) };
119 PMPX_OK
120 }
121 Err(e) => e.code(),
122 }
123}
124
125pub fn guard(f: impl FnOnce() -> u32) -> u32 {
131 std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or(PMPX_ERR_INTERNAL)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn writes_and_frees_a_command() {
142 let spec = CommandSpec::new("cargo")
143 .arg("add")
144 .arg("serde")
145 .cwd("/tmp/project");
146
147 let mut out = std::mem::MaybeUninit::<PmpxCommand>::uninit();
148 unsafe { write_command(out.as_mut_ptr(), spec) };
149 let mut cmd = unsafe { out.assume_init() };
150
151 assert_eq!(cmd.args_len, 2);
152 let program = unsafe { std::slice::from_raw_parts(cmd.program.ptr, cmd.program.len) };
153 assert_eq!(program, b"cargo");
154
155 let arg0 = unsafe { *cmd.args.add(0) };
156 let a0 = unsafe { std::slice::from_raw_parts(arg0.ptr, arg0.len) };
157 assert_eq!(a0, b"add");
158
159 let cwd = unsafe { std::slice::from_raw_parts(cmd.cwd.ptr, cmd.cwd.len) };
160 assert_eq!(cwd, b"/tmp/project");
161
162 unsafe { free_command(&mut cmd as *mut _) };
163 }
164
165 #[test]
166 fn writes_a_command_with_no_args_and_no_cwd() {
167 let spec = CommandSpec::new("cargo");
168
169 let mut out = std::mem::MaybeUninit::<PmpxCommand>::uninit();
170 unsafe { write_command(out.as_mut_ptr(), spec) };
171 let mut cmd = unsafe { out.assume_init() };
172
173 assert_eq!(cmd.args_len, 0);
174 assert!(
175 cmd.cwd.is_empty(),
176 "cwd without an override should be EMPTY"
177 );
178
179 unsafe { free_command(&mut cmd as *mut _) };
180 }
181
182 #[test]
183 fn free_command_tolerates_null() {
184 unsafe { free_command(std::ptr::null_mut()) };
185 }
186
187 #[test]
188 fn guard_turns_a_panic_into_internal_error() {
189 assert_eq!(guard(|| PMPX_OK), PMPX_OK);
190 assert_eq!(guard(|| panic!("the plugin blew up")), PMPX_ERR_INTERNAL);
191 }
192}