crun_sys/
lib.rs

1//! # crun-sys
2//!
3//! Low-level Rust FFI bindings for libcrun, the OCI container runtime library.
4//!
5//! This crate provides raw C bindings generated by bindgen. For a safe Rust
6//! interface, see the `CrunEngine` in the machineplane crate.
7//!
8//! # Platform Support
9//!
10//! **libcrun is Linux-only** (uses cgroups, namespaces, seccomp).
11//! On non-Linux platforms, this crate provides a stub module that compiles
12//! but provides no functionality.
13//!
14//! # Requirements (Linux)
15//!
16//! - libcrun >= 1.8 installed on the system
17//! - libcrun development headers
18//! - Required dependencies: yajl, libseccomp, libcap
19//!
20//! # Installation (Debian/Ubuntu)
21//!
22//! ```bash
23//! apt-get install libcrun-dev libyajl-dev libseccomp-dev libcap-dev
24//! ```
25//!
26//! # Installation (Fedora/RHEL)
27//!
28//! ```bash
29//! dnf install crun-devel yajl-devel libseccomp-devel libcap-devel
30//! ```
31//!
32//! # Safety
33//!
34//! All functions in this crate are `unsafe` as they are direct C FFI bindings.
35//! The caller must ensure:
36//! - Pointers are valid and properly aligned
37//! - Strings are null-terminated
38//! - Contexts are properly initialized and freed
39//! - Thread safety is maintained (libcrun is not thread-safe per-context)
40
41#![allow(non_upper_case_globals)]
42#![allow(non_camel_case_types)]
43#![allow(non_snake_case)]
44#![allow(dead_code)]
45#![allow(clippy::all)]
46
47// =============================================================================
48// Linux with libcrun: Include generated bindings
49// =============================================================================
50
51#[cfg(all(target_os = "linux", not(libcrun_stub)))]
52include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
53
54// =============================================================================
55// Non-Linux OR Linux without libcrun: Stub module (compiles but no functionality)
56// =============================================================================
57
58#[cfg(any(not(target_os = "linux"), libcrun_stub))]
59pub mod stub {
60    //! Stub module for non-Linux platforms.
61    //!
62    //! These types exist only to allow the code to compile on macOS/Windows.
63    //! They are not functional - CrunEngine will report unavailable.
64
65    use std::ffi::c_void;
66
67    /// Stub error type pointer.
68    pub type libcrun_error_t = *mut c_void;
69
70    /// Stub container type pointer.
71    pub type libcrun_container_t = *mut c_void;
72
73    /// Stub context structure (non-functional).
74    #[repr(C)]
75    #[derive(Debug, Default)]
76    pub struct libcrun_context_s {
77        pub state_root: *const std::ffi::c_char,
78        pub id: *const std::ffi::c_char,
79        pub bundle: *const std::ffi::c_char,
80        pub console_socket: *const std::ffi::c_char,
81        pub pid_file: *const std::ffi::c_char,
82        pub notify_socket: *const std::ffi::c_char,
83        pub handler: *const c_void,
84        pub preserve_fds: i32,
85        pub listen_fds: i32,
86        pub output_handler: Option<unsafe extern "C" fn()>,
87        pub output_handler_arg: *mut c_void,
88        pub fifo_exec_wait_fd: i32,
89        pub systemd_cgroup: bool,
90        pub detach: bool,
91        pub no_new_keyring: bool,
92        pub force_no_cgroup: bool,
93        pub no_pivot: bool,
94        pub argv: *mut *mut std::ffi::c_char,
95        pub argc: i32,
96        pub handler_manager: *mut c_void,
97    }
98
99    /// Stub features info.
100    pub type features_info_s = c_void;
101
102    // Stub functions - all return error codes indicating "not available"
103
104    /// Stub: always returns -1 (unavailable).
105    /// # Safety
106    /// This is a stub - does nothing.
107    pub unsafe fn libcrun_container_get_features(
108        _ctx: *mut libcrun_context_s,
109        _features: *mut *mut features_info_s,
110        _err: *mut libcrun_error_t,
111    ) -> i32 {
112        -1
113    }
114
115    /// Stub: always returns null.
116    /// # Safety
117    /// This is a stub - does nothing.
118    pub unsafe fn libcrun_container_load_from_memory(
119        _json: *const std::ffi::c_char,
120        _err: *mut libcrun_error_t,
121    ) -> libcrun_container_t {
122        std::ptr::null_mut()
123    }
124
125    /// Stub: always returns -1.
126    /// # Safety
127    /// This is a stub - does nothing.
128    pub unsafe fn libcrun_container_run(
129        _ctx: *mut libcrun_context_s,
130        _container: libcrun_container_t,
131        _options: i32,
132        _err: *mut libcrun_error_t,
133    ) -> i32 {
134        -1
135    }
136
137    /// Stub: always returns -1.
138    /// # Safety
139    /// This is a stub - does nothing.
140    pub unsafe fn libcrun_container_kill(
141        _ctx: *mut libcrun_context_s,
142        _id: *const std::ffi::c_char,
143        _signal: *const std::ffi::c_char,
144        _err: *mut libcrun_error_t,
145    ) -> i32 {
146        -1
147    }
148
149    /// Stub: always returns -1.
150    /// # Safety
151    /// This is a stub - does nothing.
152    pub unsafe fn libcrun_container_delete(
153        _ctx: *mut libcrun_context_s,
154        _def: *mut c_void,
155        _id: *const std::ffi::c_char,
156        _force: bool,
157        _err: *mut libcrun_error_t,
158    ) -> i32 {
159        -1
160    }
161
162    /// Stub: does nothing.
163    /// # Safety
164    /// This is a stub - does nothing.
165    pub unsafe fn libcrun_container_free(_container: libcrun_container_t) {}
166
167    /// Stub: does nothing.
168    /// # Safety
169    /// This is a stub - does nothing.
170    pub unsafe fn crun_error_release(_err: *mut libcrun_error_t) {}
171}
172
173#[cfg(any(not(target_os = "linux"), libcrun_stub))]
174pub use stub::*;
175
176#[cfg(test)]
177mod tests {
178    use std::ptr;
179
180    // =========================================================================
181    // Linux Tests
182    // =========================================================================
183
184    #[cfg(target_os = "linux")]
185    use super::*;
186
187    /// Test that we can create and free an error object (Linux only).
188    #[cfg(target_os = "linux")]
189    #[test]
190    fn test_error_lifecycle() {
191        let err: libcrun_error_t = ptr::null_mut();
192        assert!(err.is_null());
193    }
194
195    /// Test that crun_error_release handles null safely.
196    /// This is a smoke test to verify the symbol resolves correctly.
197    #[cfg(target_os = "linux")]
198    #[test]
199    fn test_error_release_null_safe() {
200        unsafe {
201            // Passing null to crun_error_release should be safe (no-op)
202            let mut err: libcrun_error_t = ptr::null_mut();
203            crun_error_release(&mut err);
204        }
205    }
206
207    /// Verify libcrun_context_s has expected size (catches ABI changes).
208    #[cfg(target_os = "linux")]
209    #[test]
210    fn test_context_struct_is_non_zero() {
211        // Context struct should have a reasonable size
212        let size = std::mem::size_of::<libcrun_context_s>();
213        assert!(size > 0, "libcrun_context_s should have non-zero size");
214        // It should be at least pointer-sized (contains many pointers)
215        assert!(
216            size >= std::mem::size_of::<*const ()>(),
217            "libcrun_context_s too small"
218        );
219    }
220
221    /// Test that load_from_memory returns null for invalid JSON.
222    #[cfg(target_os = "linux")]
223    #[test]
224    fn test_load_invalid_json_returns_null() {
225        use std::ffi::CString;
226
227        unsafe {
228            let mut err: libcrun_error_t = ptr::null_mut();
229            let invalid_json = CString::new("not valid json").unwrap();
230
231            let container = libcrun_container_load_from_memory(invalid_json.as_ptr(), &mut err);
232
233            // Should return null for invalid input
234            assert!(container.is_null(), "Expected null for invalid JSON");
235
236            // Clean up error if set
237            if !err.is_null() {
238                crun_error_release(&mut err);
239            }
240        }
241    }
242
243    // =========================================================================
244    // Non-Linux Stub Tests
245    // =========================================================================
246
247    /// Stub test: verify stub module compiles and types are usable.
248    #[cfg(any(not(target_os = "linux"), libcrun_stub))]
249    #[test]
250    fn test_stub_compiles() {
251        use super::stub::*;
252        let ctx = libcrun_context_s::default();
253        assert!(ctx.state_root.is_null());
254        assert!(ctx.id.is_null());
255        assert!(ctx.bundle.is_null());
256    }
257
258    /// Stub test: verify all stub functions return expected error values.
259    #[cfg(any(not(target_os = "linux"), libcrun_stub))]
260    #[test]
261    fn test_stub_functions_return_errors() {
262        use super::stub::*;
263
264        unsafe {
265            let mut ctx = libcrun_context_s::default();
266            let mut err: libcrun_error_t = ptr::null_mut();
267
268            // get_features should return -1
269            let result = libcrun_container_get_features(&mut ctx, ptr::null_mut(), &mut err);
270            assert_eq!(result, -1, "get_features should return -1");
271
272            // load_from_memory should return null
273            let container = libcrun_container_load_from_memory(ptr::null(), &mut err);
274            assert!(container.is_null(), "load_from_memory should return null");
275
276            // run should return -1
277            let result = libcrun_container_run(&mut ctx, ptr::null_mut(), 0, &mut err);
278            assert_eq!(result, -1, "run should return -1");
279
280            // kill should return -1
281            let result = libcrun_container_kill(&mut ctx, ptr::null(), ptr::null(), &mut err);
282            assert_eq!(result, -1, "kill should return -1");
283
284            // delete should return -1
285            let result =
286                libcrun_container_delete(&mut ctx, ptr::null_mut(), ptr::null(), false, &mut err);
287            assert_eq!(result, -1, "delete should return -1");
288        }
289    }
290
291    /// Stub test: verify free/release functions don't panic on null.
292    #[cfg(any(not(target_os = "linux"), libcrun_stub))]
293    #[test]
294    fn test_stub_free_functions_null_safe() {
295        use super::stub::*;
296
297        unsafe {
298            // These should not panic
299            libcrun_container_free(ptr::null_mut());
300            crun_error_release(ptr::null_mut());
301        }
302    }
303
304    /// Stub test: verify context struct has expected fields.
305    #[cfg(any(not(target_os = "linux"), libcrun_stub))]
306    #[test]
307    fn test_stub_context_fields() {
308        use super::stub::*;
309
310        let ctx = libcrun_context_s::default();
311
312        // All pointer fields should default to null
313        assert!(ctx.state_root.is_null());
314        assert!(ctx.id.is_null());
315        assert!(ctx.bundle.is_null());
316        assert!(ctx.console_socket.is_null());
317        assert!(ctx.pid_file.is_null());
318        assert!(ctx.notify_socket.is_null());
319        assert!(ctx.handler.is_null());
320        assert!(ctx.output_handler.is_none());
321        assert!(ctx.output_handler_arg.is_null());
322        assert!(ctx.argv.is_null());
323        assert!(ctx.handler_manager.is_null());
324
325        // Numeric fields should default to 0/false
326        assert_eq!(ctx.preserve_fds, 0);
327        assert_eq!(ctx.listen_fds, 0);
328        assert_eq!(ctx.fifo_exec_wait_fd, 0);
329        assert_eq!(ctx.argc, 0);
330        assert!(!ctx.systemd_cgroup);
331        assert!(!ctx.detach);
332        assert!(!ctx.no_new_keyring);
333        assert!(!ctx.force_no_cgroup);
334        assert!(!ctx.no_pivot);
335    }
336}