blob: 9f7b0bc08a40ed2ae31f3047e6534b8c700704fe [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use anyhow::{anyhow, Result};
use nix::unistd::isatty;
use raw_tty::TtyModeGuard;
use regex::Regex;
use serde_annotate::Annotate;
use std::any::Any;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::time::Duration;
use structopt::StructOpt;
use opentitanlib::app::command::CommandDispatch;
use opentitanlib::app::TransportWrapper;
use opentitanlib::io::uart::UartParams;
use opentitanlib::transport::Capability;
use opentitanlib::uart::console::{ExitStatus, UartConsole};
#[derive(Debug, StructOpt)]
pub struct Console {
#[structopt(flatten)]
params: UartParams,
#[structopt(short, long, help = "Do not print console start end exit messages.")]
quiet: bool,
#[structopt(short, long, help = "Log console output to a file")]
logfile: Option<String>,
#[structopt(long, help = "Send a string into the console at startup.")]
send: Option<String>,
#[structopt(
short,
long, parse(try_from_str=humantime::parse_duration),
help = "Duration of ROM detection timeout",
)]
timeout: Option<Duration>,
#[structopt(long, help = "Print a timestamp on each line of console output.")]
timestamp: bool,
#[structopt(long, help = "Exit with success if the specified regex is matched.")]
exit_success: Option<String>,
#[structopt(long, help = "Exit with failure if the specified regex is matched.")]
exit_failure: Option<String>,
}
impl CommandDispatch for Console {
fn run(
&self,
_context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
// We need the UART for the console command to operate.
transport.capabilities()?.request(Capability::UART).ok()?;
let mut stdout = std::io::stdout();
let mut stdin = std::io::stdin();
// Set up resources specified by the command line parameters.
let mut console = UartConsole {
logfile: self.logfile.as_ref().map(File::create).transpose()?,
timeout: self.timeout,
exit_success: self
.exit_success
.as_ref()
.map(|s| Regex::new(s.as_str()))
.transpose()?,
exit_failure: self
.exit_failure
.as_ref()
.map(|s| Regex::new(s.as_str()))
.transpose()?,
timestamp: self.timestamp,
newline: true,
..Default::default()
};
if !self.quiet {
println!("Starting interactive console");
println!("[CTRL+C] to exit.\n");
}
let status = {
// Put the terminal into raw mode. The tty guard will restore the
// console settings when it goes out of scope.
let _stdin_guard = if isatty(stdin.as_raw_fd())? {
let mut guard = TtyModeGuard::new(stdin.as_raw_fd())?;
guard.set_raw_mode()?;
Some(guard)
} else {
None
};
let _stdout_guard = if isatty(stdout.as_raw_fd())? {
let mut guard = TtyModeGuard::new(stdout.as_raw_fd())?;
guard.set_raw_mode()?;
Some(guard)
} else {
None
};
let uart = self.params.create(transport)?;
if let Some(send) = self.send.as_ref() {
log::info!("Sending: {:?}", send);
uart.write(send.as_bytes())?;
}
console.interact(&*uart, Some(&mut stdin), Some(&mut stdout))?
};
if !self.quiet {
println!("\n\nExiting interactive console.");
}
match status {
ExitStatus::None | ExitStatus::CtrlC => Ok(None),
ExitStatus::Timeout => {
if console.exit_success.is_some() {
// If there was a console exit success condition, then a timeout
// represents an error.
Err(anyhow!("Console timeout exceeded"))
} else {
// If there was no console exit success condition, then a timeout
// is not an error.
Ok(None)
}
}
ExitStatus::ExitSuccess => {
log::info!(
"ExitSuccess({:?})",
console.captures(status).unwrap().get(0).unwrap().as_str()
);
Ok(None)
}
ExitStatus::ExitFailure => {
log::info!(
"ExitFailure({:?})",
console.captures(status).unwrap().get(0).unwrap().as_str()
);
Err(anyhow!("Matched exit_failure expression"))
}
}
}
}