pmpx_plugin/export.rs
1//! `export!`: the whole C shell, generated.
2//!
3//! A plugin author writes one line:
4//!
5//! ```ignore
6//! pub fn create() -> Box<dyn PackageManager> { Box::new(CargoPlugin) }
7//! pmpx_plugin::export!(create);
8//! ```
9//!
10//! and this macro produces everything the host looks for: the `identity`, `command` and `attach`
11//! capability tables, the root table, the capability lookup, and the one exported symbol. None of it
12//! is something an author should have to see, and none of it is a place for a plugin to make a
13//! decision -- it is marshalling, panic containment and ownership, exactly as the ABI describes.
14//!
15//! # What the generated code promises the host
16//!
17//! - **A panic never crosses.** Every entry point is wrapped in `catch_unwind`; a panic in `name`,
18//! `family` or `command` becomes the contract's marker or an error code, never an abort.
19//! - **Memory is freed by the side that allocated it.** What the shells lease out is reclaimed by
20//! the `free_str` / `free_command` shims, and never by the host.
21//! - **A verb this build does not know is `unsupported`**, not "invalid arguments": that is what
22//! keeps a new verb additive for the host's degradation path.
23
24/// Generate the C shell for one [`PackageManager`](crate::PackageManager) implementation.
25#[macro_export]
26macro_rules! export {
27 ($create:expr) => {
28 /// The plugin's factory, wrapped.
29 fn __pmpx_instance() -> ::std::boxed::Box<dyn $crate::PackageManager> {
30 let create: fn() -> ::std::boxed::Box<dyn $crate::PackageManager> = $create;
31 create()
32 }
33
34 #[doc(hidden)]
35 unsafe extern "C" fn __pmpx_name() -> $crate::abi::PmpxStr {
36 $crate::shell::guard_str(|| $crate::shell::leak_str(__pmpx_instance().name()))
37 }
38
39 #[doc(hidden)]
40 unsafe extern "C" fn __pmpx_family() -> $crate::abi::PmpxStr {
41 $crate::shell::guard_str(|| {
42 $crate::shell::leak_str(__pmpx_instance().family().as_str())
43 })
44 }
45
46 #[doc(hidden)]
47 unsafe extern "C" fn __pmpx_run(
48 context: *const $crate::abi::PmpxContext,
49 out: *mut $crate::abi::PmpxCommand,
50 ) -> ::std::primitive::u32 {
51 // `guard` is not optional: a panic crossing the `extern "C"` boundary aborts the whole
52 // process, and the host cannot save it.
53 $crate::shell::guard(move || {
54 let plugin = __pmpx_instance();
55 // Remember the plugin's own name for the case where no host installed hooks (a
56 // plugin's own tests); the host path never needs it, since the host knows its id.
57 $crate::shell::remember_name(plugin.name());
58 // SAFETY: the host promises the context and `out` under the `command` contract.
59 unsafe { $crate::shell::dispatch(&*plugin, context, out) }
60 })
61 }
62
63 #[doc(hidden)]
64 unsafe extern "C" fn __pmpx_attach(host: *const $crate::abi::PmpxHost) {
65 if host.is_null() {
66 return;
67 }
68
69 // SAFETY: the host hands over its own table, valid for the process.
70 let lookup = unsafe { (*host).capability };
71 let key = $crate::abi::PmpxStr::new(
72 $crate::abi::PMPX_CAP_LOG.as_ptr(),
73 $crate::abi::PMPX_CAP_LOG.len(),
74 );
75 // SAFETY: a borrow that outlives the call.
76 let table = unsafe { lookup(key) };
77 if table.is_null() {
78 return;
79 }
80
81 let table = table as *const $crate::abi::PmpxLog;
82 // Read no further than the host says it built: a host with an older, smaller table is
83 // "no hooks" rather than a read past its end.
84 // SAFETY: the pointer is non-null and belongs to the host.
85 if unsafe { (*table).size } < ::std::mem::size_of::<$crate::abi::PmpxLog>() {
86 return;
87 }
88
89 // SAFETY: checked above, and the host keeps it alive.
90 unsafe { $crate::shell::install_log_table(table) };
91 }
92
93 #[doc(hidden)]
94 unsafe extern "C" fn __pmpx_free_str(s: $crate::abi::PmpxStr) {
95 // SAFETY: the host only passes back what this side leased out.
96 unsafe { $crate::shell::dispatch_free_str(s) }
97 }
98
99 #[doc(hidden)]
100 unsafe extern "C" fn __pmpx_free_command(command: *mut $crate::abi::PmpxCommand) {
101 // SAFETY: as above.
102 unsafe { $crate::shell::dispatch_free_command(command) }
103 }
104
105 #[doc(hidden)]
106 unsafe extern "C" fn __pmpx_capability(
107 name: $crate::abi::PmpxStr,
108 ) -> *const ::std::ffi::c_void {
109 // SAFETY: the host passes a borrow that outlives the call.
110 let Some(name) = (unsafe { $crate::shell::read_str(name) }) else {
111 return ::std::ptr::null();
112 };
113
114 match name {
115 $crate::abi::PMPX_CAP_IDENTITY => ::std::ptr::from_ref(&__PMPX_IDENTITY).cast(),
116 $crate::abi::PMPX_CAP_COMMAND => ::std::ptr::from_ref(&__PMPX_COMMAND).cast(),
117 $crate::abi::PMPX_CAP_ATTACH => ::std::ptr::from_ref(&__PMPX_ATTACH).cast(),
118 // A capability this build does not have is "not here", which is what lets a host
119 // ask for one it knows and this plugin keep working.
120 _ => ::std::ptr::null(),
121 }
122 }
123
124 #[doc(hidden)]
125 static __PMPX_IDENTITY: $crate::abi::PmpxIdentity = $crate::abi::PmpxIdentity {
126 size: ::std::mem::size_of::<$crate::abi::PmpxIdentity>(),
127 name: __pmpx_name,
128 family: __pmpx_family,
129 free_str: __pmpx_free_str,
130 };
131
132 #[doc(hidden)]
133 static __PMPX_COMMAND: $crate::abi::PmpxCommandCap = $crate::abi::PmpxCommandCap {
134 size: ::std::mem::size_of::<$crate::abi::PmpxCommandCap>(),
135 run: __pmpx_run,
136 free_command: __pmpx_free_command,
137 };
138
139 #[doc(hidden)]
140 static __PMPX_ATTACH: $crate::abi::PmpxAttach = $crate::abi::PmpxAttach {
141 size: ::std::mem::size_of::<$crate::abi::PmpxAttach>(),
142 attach: __pmpx_attach,
143 };
144
145 #[doc(hidden)]
146 static __PMPX_PLUGIN: $crate::abi::PmpxPlugin = $crate::abi::PmpxPlugin {
147 abi_major: $crate::abi::PMPX_ABI_MAJOR,
148 rustc_version: $crate::abi::PmpxStr::new(
149 $crate::shell::BUILD_RUSTC.as_ptr(),
150 $crate::shell::BUILD_RUSTC.len(),
151 ),
152 target: $crate::abi::PmpxStr::new(
153 $crate::shell::BUILD_TARGET.as_ptr(),
154 $crate::shell::BUILD_TARGET.len(),
155 ),
156 capability: __pmpx_capability,
157 };
158
159 // Compile-time proof that the shell's shape is the shape the table declares: if the two ever
160 // drift, this stops compiling instead of becoming a call through the wrong function type --
161 // which the ABI's major version could not catch, because it would not change.
162 const _: $crate::abi::RunFn = __pmpx_run;
163
164 /// The one symbol a host looks for.
165 ///
166 /// The version in the name tracks the *root table's layout*, not the keys: a host that finds
167 /// no such symbol says "built against another contract" instead of reading fields that
168 /// moved.
169 #[unsafe(no_mangle)]
170 pub extern "C" fn pmpx_plugin_entry_v3() -> *const $crate::abi::PmpxPlugin {
171 &__PMPX_PLUGIN
172 }
173 };
174}