blob: 1140e91940ff2e84fd5dbafd40470701852be77b [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Provides the command "dlc_install" for crosh.
use std::process::{self};
use lazy_static::lazy_static;
use regex::Regex;
use crate::dispatcher::{self, wait_for_result, Arguments, Command, Dispatcher};
const EXECUTABLE: &str = "/usr/bin/dlcservice_util";
// This is a magical string that dlcservice passes into udpate_engine daemon to
// transform into the full https:// QA Omaha server URL.
const TESTSERVER: &str = "autest";
const CMD: &str = "dlc_install";
const USAGE: &str = "<dlc-id>";
const HELP: &str = "Trigger a DLC installation against a **test** update server.
The **test** update server will serve signed DLC payloads which will only
successfully install if signed and verifiable. Otherwise, installations will
fail. The magical string 'autest' is passed into update_engine during this
installation, which will make requests against QA Omaha server and not the
production Omaha server.
WARNING: This may install an untested version of the DLC which was never
intended for end users!";
pub fn register(dispatcher: &mut Dispatcher) {
dispatcher.register_command(
Command::new(CMD.to_string(), USAGE.to_string(), HELP.to_string())
.set_command_callback(Some(dlc_install_callback)),
);
}
fn dlc_install_callback(_cmd: &Command, _args: &Arguments) -> Result<(), dispatcher::Error> {
match validate_args(_args) {
Ok(dlc_id) => execute_dlc_install(&dlc_id),
Err(err) => Err(err),
}
}
fn validate_args(_args: &Arguments) -> Result<String, dispatcher::Error> {
let args = _args.get_args();
if args.len() != 1 {
return Err(dispatcher::Error::CommandInvalidArguments(
"Please pass in a single DLC ID to install.".to_string(),
));
}
let dlc_id = &args[0];
match validate_dlc(dlc_id) {
Ok(()) => Ok(dlc_id.to_string()),
Err(err) => Err(err),
}
}
fn validate_dlc(_dlc_id: &str) -> Result<(), dispatcher::Error> {
if _dlc_id.len() > 40 {
return Err(dispatcher::Error::CommandInvalidArguments(
"DLC ID is too long.".to_string(),
));
}
lazy_static! {
static ref RE: Regex = Regex::new(r"^[a-zA-Z0-9][a-zA-Z0-9-]+$").unwrap();
}
if !RE.is_match(_dlc_id) {
return Err(dispatcher::Error::CommandInvalidArguments(
"DLC ID is not a valid format.".to_string(),
));
}
match is_dlc_supported(_dlc_id) {
Ok(()) => Ok(()),
Err(_) => Err(dispatcher::Error::CommandInvalidArguments(
"DLC ID is unsupported.".to_string(),
)),
}
}
fn is_dlc_supported(_dlc_id: &str) -> Result<(), dispatcher::Error> {
wait_for_result(
process::Command::new(EXECUTABLE)
.args(vec![
"--dlc_state".to_string(),
flag_and_arg("--id", _dlc_id),
])
.spawn()
.or(Err(dispatcher::Error::CommandReturnedError))?,
)
}
fn execute_dlc_install(_dlc_id: &str) -> Result<(), dispatcher::Error> {
wait_for_result(
process::Command::new(EXECUTABLE)
.args(vec![
"--install".to_string(),
flag_and_arg("--omaha_url", TESTSERVER),
flag_and_arg("--id", _dlc_id),
])
.spawn()
.or(Err(dispatcher::Error::CommandReturnedError))?,
)
}
fn flag_and_arg(_flag: &str, _arg: &str) -> String {
vec![_flag.to_string(), _arg.to_string()].join("=")
}