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}