Skip to main content

vpp_plugin/vlib/
cli.rs

1//! Command line interface
2
3use std::cell::UnsafeCell;
4
5use crate::bindings::{
6    vlib_cli_command_t, vlib_helper_get_global_main, vlib_helper_remove_cli_command,
7};
8
9/// Registration for CLI command
10///
11/// This is typically created automatically using the [`crate::vlib_cli_command`] macro.
12pub struct CommandRegistration(UnsafeCell<vlib_cli_command_t>);
13
14impl CommandRegistration {
15    /// Creates a new `CommandRegistration` from the given registration data
16    pub const fn new(command: vlib_cli_command_t) -> Self {
17        Self(UnsafeCell::new(command))
18    }
19
20    /// Registers the CLI command with VPP
21    ///
22    /// # Safety
23    ///
24    /// - Must be called only once for this node registration.
25    /// - Must be called from a constructor function that is invoked before VPP initialises.
26    /// - The following pointers in the registration data must be valid:
27    ///   - `path` (must be a valid, nul-terminated string)
28    ///   - `short_help` (must be a valid, nul-terminated string or null)
29    ///   - `long_help` (must be a valid, nul-terminated string or null)
30    /// - Other pointers must either point to a valid object of the appropriate type or be null.
31    pub unsafe fn register(&'static self) {
32        // SAFETY: preconditions of the method are met
33        unsafe {
34            let vgm = vlib_helper_get_global_main();
35            let cm = &mut (*vgm).cli_main;
36            let reg = self.0.get();
37            (*reg).next_cli_command = cm.cli_command_registrations;
38            cm.cli_command_registrations = reg;
39        }
40    }
41
42    /// Unregisters the CLI command from VPP
43    ///
44    /// # Safety
45    ///
46    /// - Must be called only once for this registration.
47    /// - Must be called from a destructor function that is invoked after VPP uninitialises.
48    /// - The CLI command must have been previously registered with VPP using [`Self::register`].
49    pub unsafe fn unregister(&self) {
50        // SAFETY: preconditions of the method are met
51        unsafe {
52            let vgm = vlib_helper_get_global_main();
53            let cm = &mut (*vgm).cli_main;
54            vlib_helper_remove_cli_command(cm, self.0.get());
55        }
56    }
57}
58
59// SAFETY: there is nothing in vlib_cli_command that is tied to a specific thread or that
60// mutates global state, so it's safe to send between threads.
61unsafe impl Send for CommandRegistration {}
62// SAFETY: CommandRegistration doesn't allow any modification after creation (and vpp doesn't
63// modify it afterwards either), so it's safe to access from multiple threads. The only exception
64// to this is the register/unregister methods, but it's the duty of the caller to ensure they are
65// called at times when no other threads have a reference to the object.
66unsafe impl Sync for CommandRegistration {}
67
68#[cfg(test)]
69mod tests {
70    use crate::{bindings::vlib_cli_command_t, vlib::cli::CommandRegistration};
71
72    #[test]
73    fn test_cmd_reg() {
74        // CommandRegistration::new is a const function and Rust doesn't generate coverage data
75        // when such functions are evaluated at compile time
76        // (https://github.com/rust-lang/rust/issues/124732), so force it to be evaluated at
77        // runtime.
78        let command = std::hint::black_box(CommandRegistration::new(vlib_cli_command_t::default()));
79
80        let command = Box::new(command);
81        // Get a raw pointer so we can ignore the static lifetime requirement of register
82        let command = Box::into_raw(command);
83
84        // SAFETY: preconditions of register/unregister don't have to be met because we are
85        // calling this outside of the VPP application, meaning that pointers in the
86        // vlib_cli_command_t won't be dereferenced.
87        unsafe {
88            (*command).register();
89            (*command).unregister();
90
91            let _ = Box::from_raw(command);
92        }
93    }
94}