Use this when a D-Bus API is already planned or crosh lacks the needed permissions or capabilities.
A sample workflow is included below for writing a new command.
Pick an appropriate module for the command to belong to. For dev mode commands this will be dev
, most other commands will belong in base
. This example will use base
as the module, but the same steps should still apply in other cases.
Then pick a command name, create a sub module with that name, and register it with the parent module. For this example the command is verify_ro
, so the new source file is src/base/verify_ro.rs
and two lines need to be added to src/base/mod.rs
:
First, the submodule needs to be imported:
mod verify_ro;
Second the register function (to be created below) needs to be called by the register function in the parent module src/base/mod.rs
:
pub fn register(dispatcher: &mut Dispatcher) { ... verify_ro::register(dispatcher); ... }
Now the src/base/verify_ro.rs
source file is ready to be written. Start with this minimal source file and verify that crosh compiles with cargo build
:
use crate::dispatcher::{self, Arguments, Command, Dispatcher}; pub fn register(dispatcher: &mut Dispatcher) { dispatcher.register_command( Command::new( "verify_ro", "TODO put usage here", "TODO put description here", ) .set_command_callback(Some(execute_verify_ro)), ); } fn execute_verify_ro(_cmd: &Command, _args: &Arguments) -> Result<(), dispatcher::Error> { unimplemented!(); }
This assumes the above instructions are already complete.
Since privileged operations cannot be executed by Crosh, one common method is to move the logic to debugd, and communicate with it via D-Bus. We have a local module with bindings to simplify boilerplate across commands.
Note that debugd's D-Bus interface already has Rust bindings generated through dev-rust/system_api, so the bindings and D-Bus connection can be imported with:
use crate::debugd::Debugd;
If you want to browse the source code of the generated bindings, after running build_packages, take a look at the following path:
/build/${BOARD}/usr/lib/cros_rust_registry/registry/system_api-*/src/bindings/client/
Inside the debugd.rs module, we have methods to wrap the D-Bus proxy call. Think of it as bindings on top of bindings. Every debugd method has to be defined here.
The underlying method call uses the fact that the imported trait system_api::client::OrgChromiumDebugd
is implemented for self.connection
so the member functions that map to D-Bus methods can be called from it.
pub fn call_dmesg(&self, options: PropMap) -> Result<String, dbus::Error> { self.connection .with_proxy( BUS_NAME, SERVICE_PATH, DEFAULT_DBUS_TIMEOUT, ) .call_dmesg(options) }
Creating a new bus connection looks like the following. Note that bus connection errors are already logged via Debugd
, so each command only has to convert it to dispatcher::Error::CommandReturnedError
and return.
let connection = Debugd::new().map_err(|_| dispatcher::Error::CommandReturnedError)?;
Finally, your command makes calls to the new method
let output = connection.call_dmesg(dbus_options).map_err(|err| { println!("ERROR: Got unexpected result: {}", err); dispatcher::Error::CommandReturnedError })?;
This covers the basics. If you look at the actual source code for base::display_debug, it provides a more complicated example with a start method call, a watcher, and a stop method call.
This assumes the above instructions are already complete.
Since privileged operations cannot be executed by Crosh, some commands talk directly to respective services via D-Bus.
use dbus::blocking::Connection;
Inside the command implementation a D-Bus connection needs to be initialized. A blocking connection is used in this example.
let connection = Connection::new_system().map_err(|err| { error!("ERROR: Failed to get D-Bus connection: {}", err); dispatcher::Error::CommandReturnedError })?;
The bus connection can then be used to get an interface to the desired service, which is debugd in this case:
let conn_path = connection.with_proxy( "registered.bus.name", "/registered/service/path", DEFAULT_DBUS_TIMEOUT, );
The default help strings are populated using the command name, usage string, description string, and any options or flags that are registered through the dispatcher API.
Alternatively, a help callback can be set when registering the command to perform custom logic like invoking the help option of a binary. For example:
const EXECUTABLE: &str = "/usr/bin/vmc"; pub fn register(dispatcher: &mut Dispatcher) { dispatcher.register_command( Command::new("vmc", "", "") .set_command_callback(Some(execute_vmc)) .set_help_callback(vmc_help), ); } fn vmc_help(_cmd: &Command, w: &mut dyn Write, _level: usize) { let mut sub = process::Command::new(EXECUTABLE) .arg("--help") .stdout(Stdio::piped()) .spawn() .unwrap(); if copy(&mut sub.stdout.take().unwrap(), w).is_err() { panic!(); } if sub.wait().is_err() { panic!(); } }
If you want to replace a crosh command with some other UI (like a chrome:// page), and you want to deprecate the command gracefully by leaving behind a friendly note if people try to use it, here's the form.
# Set the vars to pass the unittests ... USAGE_storage_status='' HELP_storage_status='' # ... then unset them to hide the command from "help" output. unset USAGE_storage_status HELP_storage_status cmd_storage_status() ( # TODO: Delete this after the R## release branch. echo "Removed. See storage_info section in chrome://system" )
Make sure you add the TODO comment so people know in the future when it's OK to clean it up.
You can run ./crosh
on your desktop system to get a sample shell. You can quickly test basic interactions (like argument parsing) here, or check the help output. You won't have access to the CrOS services that many crosh commands expect to talk to (via D-Bus), so those commands will fail.
If you want to load dev mode modules, you can use ./crosh --dev
. It will only load local modules (./dev.d/
), so if your module lives elsewhere, you can copy it here temporarily.
Similarly, if you want to load removable device modules, you can use ./crosh --removable
.
To run the unit tests either call cargo test --workspace
in the crosh folder or run emege-${BOARD} crosh && FEATURES=test emerge-${BOARD}
The ./run_tests.sh
legacy unittest runner performs a bunch of basic style and soundness checks. Run it against any changes to the shell code!
Anyone should feel free to pick up these ideas and try to implement them :).
shell
.Note: All new commands must be implemented in Rust. No new commands may be implemented in the legacy shell crosh code.
Crosh was originally written in shell. At the time of writing many of the commands are still remain in shell and have yet to be ported over to the Rust crosh. This documentation is kept here for the maintenance of these commands.
For every command, you define two variables and one function. There is no need to register the new commands anywhere as crosh will inspect its own runtime environment to discover them.
Here's how you would register a new foo
command.
# A short description of arguments that this command accepts. USAGE_foo='<some args>' HELP_foo=' Extended description of this command. ' # Not required, but lets crosh detect if the foo program is available in the # current system (e.g. the package is not installed on all devices). If it # isn't available, crosh will automatically display an error message and never # call cmd_foo. EXEC_foo='/full/path/to/program' cmd_foo() ( # Implementation for the foo command. # You should validate $# and "$@" and process them first. # For invalid args, call the help function with an error message # before returning non-zero. ...foo code goes here!... )
See the design section below for more details on what and how to structure the new command.
If your crosh command simply calls out to an external program to do the processing, and that program already offers usage details, you probably don't want to have to duplicate things. You can handle this scenario by defining a help_foo
function that makes the respective call.
# Set the help string so crosh can discover us automatically. HELP_foo='' cmd_foo() ( ... ) help_foo() ( /some/command --help )
Take note that we still set HELP_foo
. This is needed so crosh can discover us automatically and display us in the relevant user facing lists (like the help_advanced
command). We don't need to set USAGE_foo
though since the help_foo
function does that for us.
If a command is not yet ready for “prime time”, you might want to have it in crosh for early testing, but not have it show up in the help
output where users can easily discover it (of course, the code is all public, so anyone reading the actual source can find it). Here's how you do it.
# Set the vars to pass the unittests ... USAGE_vmc='' HELP_vmc='' # ... then unset them to hide the command from "help" output. unset USAGE_vmc HELP_vmc cmd_vmc() ( ... )