Skip to main content

chillffi/
lib.rs

1//! **A simple isolated dynamic FFI framework for Rust.**
2//!
3//! `chillffi` allows dynamically loading **C ABI-compatible libraries** (`.so`, `.dylib`, `.dll`)
4//! and calling their functions at runtime, **isolating each FFI call in a separate process**.
5//! If third-party native code crashes or corrupts memory, 
6//! the failure is contained within the isolated process, 
7//! keeping your main Rust application running.
8//!
9//! # Platform support
10//!
11//! Unix-like OSes and Windows (MSVC toolchain — `libffi-sys` has no `gnu` build).
12//! On Unix a clone is a `fork` of the main zygote; on Windows it's a fresh
13//! process re-running the same executable, since there is no `fork`.
14//!
15//! # Quick start
16//!
17//! ```no_run
18//! use chillffi::ffi;
19//!
20//! fn main() -> ()
21//! {
22//!   // Perform an FFI call inside an isolated context using a macro
23//!   let result: f64 = ffi!(|scope| {
24//!     // Dynamically load the system library, bound to this scope
25//!     let libm: Library = scope.load("libm.so.6")?;
26//!   
27//!     // Call the "sqrt" function, specifying the expected return type
28//!     libm.call("sqrt").arg::<f64>(4.0).result()
29//!     
30//!     // Here libm will be automatically cleared due to drop() when exiting the closure.
31//!     // You can also do this manually via drop(libm) or libm.unload()?
32//!   }).expect("FFI call failed");
33//!
34//!   // Process the typed result
35//!   println!("sqrt(4.0) = {}", result);
36//!   assert!((result - 2.0).abs() < f64::EPSILON, "sqrt(4.0) != 2.0");
37//! }
38//! ```
39//! 
40//! Example of a memory-sensitive call to the `clock_gettime` function 
41//! from the system library `libc.so.6` using [`AllocatedMemory`](ffi::allocatedMemory::AllocatedMemory):
42//!
43//! ```no_run
44//! use chillffi::ffi::allocatedMemory::{AllocatedMemory};
45//! use chillffi::ffi::errors::FFIError;
46//! use chillffi::ffi;
47//!
48//! fn main() -> ()
49//! {
50//!   // clock_gettime(CLOCK_REALTIME, &timespec) — struct out-param via Alloc/ReadMemory,
51//!   // the case a plain Value::Pointer can't cover on its own.
52//!   let (secs, nanos): (i64, i64) = ffi!(|scope| {
53//!     let libc: Library = scope.load("libc.so.6")?;
54//!
55//!     // struct timespec { time_t tv_sec; long tv_nsec; } — 16 bytes on x86_64 Linux
56//!     let mem: AllocatedMemory = scope.alloc(16)?;
57//!
58//!     libc.call("clock_gettime")
59//!       .arg::<i32>(0 /* CLOCK_REALTIME */)
60//!       .arg(mem.asPointer())
61//!       .void()?;
62//!
63//!     let bytes: Vec<u8> = mem.read()?;
64//!     drop(mem);
65//!
66//!     let secs: i64 = i64::from_ne_bytes(bytes[0..8].try_into().unwrap());
67//!     let nanos: i64 = i64::from_ne_bytes(bytes[8..16].try_into().unwrap());
68//!     Ok((secs, nanos))
69//!   }).expect("clock_gettime failed");
70//!
71//!   println!("clock_gettime(CLOCK_REALTIME) = {}.{:09}", secs, nanos);
72//! }
73//! ```
74//!
75//! For more detailed examples, see the `examples` folder.
76//! 
77//! The tests there are divided by features, and inside there are different usage variations.
78//!
79//! You can also run them via `cargo run --example <name>`.
80//!
81//! # Why is this convenient
82//!
83//! In general practice, we are used to doing it like in Python and other
84//! programming languages — precisely specifying all the wrappers for FFI.
85//! After which we observe how FFI still crashes anyway and the libraries are
86//! not built, and the code does not work.
87//!
88//! This is all because FFI requires a manual bridge and it is not always
89//! possible to make one.
90//!
91//! **chillffi** works on a different principle — you can write any FFI code
92//! inside isolated blocks. Because FFI should not be scattered throughout your
93//! code — this is an unsafe approach. Therefore, we write it in isolation and
94//! preferably briefly, only when necessary.
95//!
96//! Since everything is located in isolated processes — we do not damage the
97//! main runtime in any way and do not touch your code. All FFI requests work
98//! in a sterile manner and in case of errors will clearly let you know about
99//! it. You can also simply ignore them if you want.
100//!
101//! As a result, we can freely and simply write:
102//! - Test code
103//! - Educational code
104//! - FFI bridges
105//! - Dynamic programming languages
106//! - Game engines
107//! - Reactive systems and dynamic systems
108//! - And many other things
109//!
110//! This is also different from the WASM approach — because we preserve a true
111//! native execution here.
112//!
113//! # How it works
114//!
115//! 1. Before your code starts running, a Zygote is created — it is an empty
116//!    process for cloning itself and isolating FFI.
117//! 2. When work with FFI is required — a copy is created from the zygote.
118//! 3. Data and descriptors are transferred through a secure socket channel in
119//!    memory.
120//! 4. In case of errors, the supervisor intercepts the worker crash and returns
121//!    the error to Rust, keeping your application stable.
122//!
123//! <div class="warning">
124//!
125//! This does not protect you from the FFI code running inside the isolated process.
126//!
127//! For example, if it does something with your OS or file system -
128//! it is already your responsibility to separately protect against this.
129//!
130//! For example: You can use a virtual space for the file system and so on.
131//!
132//! </div>
133//!
134//! <div class="warning">
135//!
136//! FFI blocks should be as small as possible in size. I.e., not 100 lines in 1 FFI space.
137//!
138//! An exception can be considered when you need a single address space for several operations.
139//!
140//! In other cases, you should separate FFI requests as much as possible.
141//!
142//! Because no one can guarantee that any FFI request will not break your code.
143//!
144//! Even if you are an experienced programmer, there are things that do not depend on your experience.
145//!
146//! </div>
147//!
148//! # License
149//!
150//! The source code is distributed under the FCL license.
151//! See the repository for the full text.
152// =================================================================================================
153
154#[cfg(not(any(
155  all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"),
156  all(target_os = "linux", target_arch = "aarch64", target_env = "gnu"),
157
158  all(target_os = "macos", target_arch = "x86_64"),
159  all(target_os = "macos", target_arch = "aarch64"),
160
161  all(target_os = "windows", target_arch = "x86_64", target_env = "msvc"),
162  all(target_os = "windows", target_arch = "aarch64", target_env = "msvc"),
163)))]
164compile_error!("Unsupported platform or architecture");
165
166// =================================================================================================
167
168/// Used for running tests.
169#[cfg(test)]
170mod examplesPlatform 
171{
172  include!(concat!(env!("CARGO_MANIFEST_DIR"), "/examples/platform/mod.rs"));
173}
174
175// =================================================================================================
176
177mod worker;
178mod zygote;
179mod platform;
180pub mod ffi;
181pub mod pathResolver;
182pub mod errnoPolicy;
183
184// =================================================================================================
185
186use std::{env};
187use crate::zygote::{initZygote, runAsZygote, ZygoteFlag};
188#[cfg(windows)]
189use crate::platform::ipc::windows::CloneFlag;
190#[cfg(windows)]
191use crate::zygote::{runAsClone};
192
193// =================================================================================================
194
195/// Single entry point for zygote initialization in any binary (including tests).
196/// Checks whether the process is running as a zygote; if so — switches to daemon mode,
197/// otherwise — initializes the parent side.
198#[ctor::ctor(unsafe)]
199fn zygoteEntrypoint() -> ()
200{
201  let mut args: env::ArgsOs = env::args_os();
202  args.next();
203  if let Some(arg) = args.next()
204  {
205    if arg == ZygoteFlag
206    {
207      runAsZygote();
208    }
209
210    #[cfg(windows)]
211    if arg == CloneFlag
212    {
213      runAsClone();
214    }
215  }
216
217  // Do this once to start the main zygote
218  initZygote().expect("Failed to setup zygote");
219}
220
221// =================================================================================================
222
223/// Internal items re-exported for the [`ffi!`] macro.  
224/// Not part of the public API; do not use directly.
225#[doc(hidden)]
226pub mod __ffiInternal 
227{
228  pub use crate::zygote::{ClonedZygote, ZygoteGuard};
229}
230
231/// Main macro for working with FFI.
232///
233/// It creates a copy of the zygote from the main zygote and opens a [`Scope`](crate::ffi::scope::Scope)
234/// bound to it — `scope` is how you load libraries ([`Scope::load`](crate::ffi::scope::Scope::load))
235/// and allocate memory ([`Scope::alloc`](crate::ffi::scope::Scope::alloc)) for the duration of the block.
236///
237/// `Library<'g>` can only be constructed via `scope.load(...)`, and only lives as long as the
238/// scope that produced it — the compiler enforces this, not us. There is no variant of this
239/// macro without a scope: an FFI block always needs one to load anything into.
240///
241/// Isolation allows adding FFI insertions without breaking or corrupting the main runtime.
242#[macro_export]
243macro_rules! ffi
244{
245  // `ffi!(|scope| { ... })`. The scope name can be any identifier — the important
246  // thing is that there are no repetitions inside {}. Scope<'g> borrows the
247  // ScopeGuard of this block, therefore AllocatedMemory<'g> and Library<'g>
248  // cannot be returned outside — the compiler catches this, not us.
249  (|$scopeName:ident| { $($body:tt)* }) => 
250  {
251    (|| -> Result<_, $crate::ffi::errors::FFIError> 
252    {
253      #[allow(unused_imports)]
254      use $crate::ffi::library::Library;
255 
256      // Creating a clone-zygote from the main one
257      let zygote: $crate::__ffiInternal::ClonedZygote = 
258        $crate::__ffiInternal::ClonedZygote::getMeClone()?;
259 
260      // Registering the clone-zygote in the current thread's ZygoteStack
261      let _guard: $crate::__ffiInternal::ZygoteGuard = 
262        $crate::__ffiInternal::ZygoteGuard::enter(zygote);
263 
264      // ScopeGuard lives strictly within the boundaries of this block; $scopeName borrows it.
265      let _scopeGuard: $crate::ffi::scope::ScopeGuard = $crate::ffi::scope::ScopeGuard::new();
266      let $scopeName = $crate::ffi::scope::Scope::new(&_scopeGuard);
267 
268      // Executing the body
269      $($body)*
270    })()
271  };
272}
273
274// =================================================================================================