FDS/OS 1.0
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
mod cli;
|
||||
mod machine;
|
||||
mod power;
|
||||
mod recovery;
|
||||
use clap::Parser;
|
||||
use cli::{
|
||||
Action, DataCommand, InspectCommand, PowerCommand, ProfileCommand, RecoveryCommand, Switch,
|
||||
};
|
||||
use fds_common::{
|
||||
Error, Result, VERSION,
|
||||
control::{self, Request},
|
||||
manifest::Manifest,
|
||||
read_text, trace,
|
||||
};
|
||||
use std::{path::Path, process::ExitCode};
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let (command, json) = cli::Cli::parse().into_command();
|
||||
let Some(command) = command else {
|
||||
return Ok(cli::help(None)?);
|
||||
};
|
||||
match command {
|
||||
Action::Machine {
|
||||
command: Some(command),
|
||||
} => machine::run(command, json)?,
|
||||
Action::Machine { command: None } => cli::help(Some("machine"))?,
|
||||
Action::Recovery { command: None } => cli::help(Some("recovery"))?,
|
||||
Action::Recovery {
|
||||
command: Some(RecoveryCommand::Check(args)),
|
||||
} => recovery::run(args.bay, false, None, json)?,
|
||||
Action::Recovery {
|
||||
command: Some(RecoveryCommand::Repair { bay, confirm }),
|
||||
} => recovery::run(bay, true, confirm, json)?,
|
||||
Action::Poweroff => power::client(Request::Poweroff, json)?,
|
||||
Action::Reboot => power::client(Request::Reboot, json)?,
|
||||
Action::Power(args) => match args.command.unwrap_or(PowerCommand::Status) {
|
||||
PowerCommand::Poweroff => power::client(Request::Poweroff, json)?,
|
||||
PowerCommand::Reboot => power::client(Request::Reboot, json)?,
|
||||
PowerCommand::Status => power::client(Request::PowerStatus, json)?,
|
||||
PowerCommand::Resume => power::client(Request::PowerResume, json)?,
|
||||
PowerCommand::ShutdownHook => power::shutdown_hook()?,
|
||||
PowerCommand::HoldShutdown => power::hold_shutdown()?,
|
||||
PowerCommand::RecordFinal { outcome } => power::record_final(outcome.is_some())?,
|
||||
},
|
||||
Action::Burn { command } => fds_burn::client::burn(command, json)?,
|
||||
Action::Format { command } => fds_burn::client::format(command, json)?,
|
||||
Action::Inspect(args) => {
|
||||
if let Some(InspectCommand::Image { image: path }) = args.command {
|
||||
let file = std::fs::File::open(path)?;
|
||||
if !file.metadata()?.is_file() {
|
||||
return Err(Error("Image inspection requires a regular file".into()));
|
||||
}
|
||||
let mut info = fds_burn::image::inspect(&file, file.metadata()?.len())?;
|
||||
info.sha256 = Some(fds_burn::image::digest(&file, info.bytes, |_| Ok(()))?);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&info).map_err(|e| Error(e.to_string()))?
|
||||
);
|
||||
} else {
|
||||
let path = args
|
||||
.target
|
||||
.expect("Clap requires a target or image subcommand");
|
||||
if let Some(bay) = path
|
||||
.to_str()
|
||||
.and_then(|value| fds_burn::client::bay(value).ok())
|
||||
{
|
||||
fds_burn::client::inspect_bay(bay, json)?;
|
||||
} else {
|
||||
inspect_manifest(&path, json)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::Profiles => cartridge(Request::Profiles, json)?,
|
||||
Action::Profile { command } => cartridge(
|
||||
Request::Profile {
|
||||
profile: match command {
|
||||
ProfileCommand::Activate { name } => name,
|
||||
ProfileCommand::Deactivate => "cli".into(),
|
||||
},
|
||||
},
|
||||
json,
|
||||
)?,
|
||||
Action::Network { state } => cartridge(
|
||||
Request::Network {
|
||||
enabled: matches!(state, Switch::On),
|
||||
},
|
||||
json,
|
||||
)?,
|
||||
Action::Bays => cartridge(Request::Bays, json)?,
|
||||
Action::Bay(args) => cartridge(Request::Bay { bay: args.bay }, json)?,
|
||||
Action::Eject(args) => cartridge(Request::Eject { bay: args.bay }, json)?,
|
||||
Action::Rescan => cartridge(Request::Rescan, json)?,
|
||||
Action::Data {
|
||||
command: DataCommand::Use(args),
|
||||
} => cartridge(Request::DataUse { bay: args.bay }, json)?,
|
||||
Action::Run { bay, arguments } => cartridge(Request::Run { bay, arguments }, json)?,
|
||||
Action::Topology => cartridge(Request::Topology, json)?,
|
||||
Action::Version => println!("fds {VERSION}"),
|
||||
Action::BootProfile => trace::load(Path::new(trace::RUNTIME))?.print(json)?,
|
||||
Action::Info => info(json)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn info(json: bool) -> Result<()> {
|
||||
let kernel = read_text(Path::new("/proc/sys/kernel/osrelease"), 4096)?
|
||||
.trim()
|
||||
.to_owned();
|
||||
let pid1 = std::fs::read_link("/proc/1/exe")
|
||||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_else(|_| {
|
||||
read_text(Path::new("/proc/1/comm"), 4096)
|
||||
.map(|s| s.trim().to_owned())
|
||||
.unwrap_or_else(|_| "unavailable".into())
|
||||
});
|
||||
let target = if cfg!(all(
|
||||
target_arch = "aarch64",
|
||||
target_env = "musl",
|
||||
target_feature = "crt-static"
|
||||
)) {
|
||||
"aarch64 static-musl"
|
||||
} else {
|
||||
"host test build"
|
||||
};
|
||||
if json {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({"fds_version": VERSION, "target": target, "kernel": kernel, "pid1": pid1})
|
||||
);
|
||||
} else {
|
||||
println!("FDS/OS {VERSION}\nTOOLS {target}\nKERNEL {kernel}\nINIT {pid1}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn inspect_manifest(path: &Path, json: bool) -> Result<()> {
|
||||
let manifest = Manifest::load(path)?;
|
||||
if json {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&manifest).map_err(|e| Error(e.to_string()))?
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"{}\n{} {}\nID {}\nMEDIA {}",
|
||||
manifest.cartridge.class.label(),
|
||||
manifest.cartridge.name,
|
||||
manifest.cartridge.version,
|
||||
manifest.cartridge.id,
|
||||
if manifest.media.writable {
|
||||
"WRITABLE"
|
||||
} else {
|
||||
"READ-ONLY"
|
||||
}
|
||||
);
|
||||
if let Some(activation) = manifest.activation {
|
||||
println!("PROFILE {}", activation.profile);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn cartridge(request: Request, json: bool) -> Result<()> {
|
||||
let debug = matches!(request, Request::Topology);
|
||||
let response = control::request(&request)?;
|
||||
if json {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&response).map_err(|e| Error(e.to_string()))?
|
||||
);
|
||||
} else {
|
||||
if let Some(pid) = response.started_pid {
|
||||
println!("STARTED {pid} Output: /run/log/cartridged/current");
|
||||
}
|
||||
if let Some(p) = response.profiles {
|
||||
println!(
|
||||
"PROFILES cli, windowmaker\nDESKTOP {} {}\nNETWORK {}",
|
||||
p.desktop,
|
||||
if p.desktop == "windowmaker" {
|
||||
if p.ready_ns.is_some() {
|
||||
"READY"
|
||||
} else {
|
||||
"STARTING"
|
||||
}
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if p.network.is_empty() {
|
||||
"OFF".into()
|
||||
} else {
|
||||
p.network.join(", ")
|
||||
}
|
||||
);
|
||||
if let Some(bay) = p.environment_bay {
|
||||
println!("ENVIRONMENT BAY {bay}");
|
||||
}
|
||||
if let (Some(start), Some(end)) = (p.activation_ns, p.ready_ns) {
|
||||
println!(
|
||||
"ACTIVATION TO READY {:.3} ms",
|
||||
end.saturating_sub(start) as f64 / 1_000_000.0
|
||||
);
|
||||
}
|
||||
if let Some(error) = p.error {
|
||||
println!("ERROR {error}");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
for bay in response.bays {
|
||||
println!(
|
||||
"BAY {} {}{}",
|
||||
bay.bay,
|
||||
bay.state.to_uppercase().replace('_', " "),
|
||||
bay.name
|
||||
.as_ref()
|
||||
.map(|n| format!(" {n}"))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
if let Some(detail) = bay.detail {
|
||||
println!(" {detail}");
|
||||
}
|
||||
if let Some(manifest) = bay.manifest {
|
||||
println!(
|
||||
" {} {} {}",
|
||||
manifest.cartridge.class.label(),
|
||||
manifest.cartridge.id,
|
||||
manifest.cartridge.version
|
||||
);
|
||||
}
|
||||
if let Some(catalogue) = bay.software {
|
||||
for software in catalogue.software {
|
||||
println!(
|
||||
" SOFTWARE {} {} (partition {})",
|
||||
software.id, software.version, software.partition
|
||||
);
|
||||
for command in software.commands.keys() {
|
||||
println!(" fds run {} -- {}:{}", bay.bay, software.id, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(mount) = bay.mount {
|
||||
println!(" MOUNT {mount}");
|
||||
}
|
||||
if bay.consumers > 0 {
|
||||
println!(" MANAGED PROCESSES {}", bay.consumers);
|
||||
}
|
||||
if debug {
|
||||
for device in bay.devices {
|
||||
println!(
|
||||
" {} USB {}:{}",
|
||||
device.topology, device.vendor, device.product
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if debug {
|
||||
for device in response.unmapped {
|
||||
println!(
|
||||
"UNMAPPED {} USB {}:{}",
|
||||
device.topology, device.vendor, device.product
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(error) => {
|
||||
eprintln!("fds: {error}");
|
||||
ExitCode::from(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user