blob: 8b61e3f43e37b3fa2f221d21bc7cf3c7caa361e0 [file] [log] [blame] [view] [edit]
# Crosh -- The ChromiumOS shell
[TOC]
This is the homepage/documentation for the crosh, the ChromiumOS shell.
If you're on a CrOS devices right now, you should be able to launch crosh
by hitting Ctrl+Alt+T. If you aren't on CrOS, then most likely that won't
do anything useful :).
# For Users
For using crosh, please see the
[public documentation](https://www.chromium.org/chromium-os/developer-library/reference/device/crosh/).
# For ChromiumOS Developers
This section is meant for people hacking on ChromiumOS, especially when they
need to modify/extend crosh.
## Security Warning
Please do not install new modules without full security review. Any insecure
code that crosh loads will be directly available to people in verified mode.
That's an easy attack vector to run arbitrary code and mess with the user's
state. We don't want to undermine the security of CrOS!
If you are looking for reviewers, look at the [OWNERS](./OWNERS) file.
## Where Files Live
Crosh is being migrated from shell to Rust. Crosh starts executing from
[src/main.rs](src/main.rs) but many commands are implemented as their own
submodule of one of the high level modules (e.g. `base` or `dev`).
The old [`crosh`](./crosh) script contains the legacy implementations of
commands that haven't been ported to Rust yet. It is installed on the device
as `crosh.sh`.
### Source Repos
Modules that are specific to a board, or heavily specific to a package, should
generally live with that board and/or package. For functions that are always
available on all CrOS devices, that code should be kept in this repo.
If you're unsure, just ask on chromium-os-dev@chromium.org.
## Adding New Commands
> **Note**: All new commands must be implemented in Rust. No new commands may
> be implemented in the legacy shell crosh code.
First determine what implementation strategy the new command will use. When
selecting a strategy, it helps to know what permissions and privileges are
needed. With the strategy in mind, check out the various examples below.
### Command Design
The crosh shell runs in the same environment as the browser (same user/group,
same Linux namespaces, etc...). So any tools you run in crosh, or information
you try to acquire, must be accessible to the `chronos` user.
However, we rarely want crosh to actually execute tools directly. Instead,
you should add D-Bus callbacks to the [debugd] daemon and send all requests to
it. We can better control access in debugd and lock tools down. Then the
only logic that exists in crosh is a D-Bus IPC call and then displays output
from those programs. Discussion of debugd is out of scope here, so check out
the [debugd] directory instead.
### Examples
Example implementations:
* D-Bus method wrapper (debugd): [base::verify_ro]\
Use this when a D-Bus API is already planned or crosh lacks the needed
permissions or capabilities.
* External binary wrapper: [base::ccd_pass]\
Use this when there is already a command line tool that implements the
command that works when run as chronos with the capabilities of crosh.
* Command written in Rust: [base::arc]\
This is best suited for cases where extra capabilities are not needed and
having a separate command line tool is not justified.
A sample workflow is included below for writing a new command.
### Module Setup
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:
```rust
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`:
```rust
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`:
```rust
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!();
}
```
### Command Implementation via debugd
This assumes the [above instructions](#module-setup) 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](/debugd/dbus_bindings/org.chromium.debugd.xml)
already has Rust bindings generated through dev-rust/system_api, so the
bindings and D-Bus connection can be imported with:
```rust
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:
```sh
/build/${BOARD}/usr/lib/cros_rust_registry/registry/system_api-*/src/bindings/client/
```
Inside the [debugd.rs module](./src/debugd.rs), 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.
```rust
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.
```rust
let connection = Debugd::new().map_err(|_| dispatcher::Error::CommandReturnedError)?;
```
Finally, your command makes calls to the new method
```rust
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.
### Command Implementation via D-Bus
This assumes the [above instructions](#module-setup) are already complete.
Since privileged operations cannot be executed by Crosh, some commands talk
directly to respective services via D-Bus.
```rust
use dbus::blocking::Connection;
```
Inside the command implementation a D-Bus connection needs to be initialized.
A blocking connection is used in this example.
```rust
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:
```rust
let conn_path = connection.with_proxy(
"registered.bus.name",
"/registered/service/path",
DEFAULT_DBUS_TIMEOUT,
);
```
### Command Help
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:
```rust
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!();
}
}
```
## Deprecating Commands
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.
```sh
# 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.
## Testing
### Iterative Development
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/`](./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`.
### Unittests
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`](./run_tests.sh) legacy unittest runner performs a bunch
of basic style and soundness checks. Run it against any changes to the shell
code!
# Future Work
Anyone should feel free to pick up these ideas and try to implement them :).
* Move any remaining commands that are implemented in place to debugd calls
so they can be done over D-Bus.
* Run crosh itself in a restricted sandbox (namespaces/seccomp/etc...).
Once all commands are done via IPC, there's no need to keep privs.
Might make it dependent upon dev mode though so we don't break `shell`.
* Migrate additional legacy shell commands over to Rust. This can also be done
at the same time as migrating a command over to debugd.
# Legacy Crosh Documentation
> **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.
## Command API
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.
```sh
# 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.
### Command Help
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.
```sh
# 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.
## Hiding Commands
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.
```sh
# 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() (
...
)
```
[base::verify_ro]: src/base/verify_ro.rs
[base::arc]: src/base/arc.rs
[base::display_debug]: src/base/display_debug.rs
[base::ccd_pass]: src/base/ccd_pass.rs
[debugd]: /debugd/