diff --git a/Cargo.lock b/Cargo.lock index 6c74da6f9..745e6a3e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1710,6 +1710,7 @@ name = "gui" version = "0.1.0" dependencies = [ "aarch64", + "anstyle-parse", "applevisor-sys", "ash", "ash-window", diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 5c664f473..2909f9a77 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -8,6 +8,7 @@ name = "obliteration" path = "src/main.rs" [dependencies] +anstyle-parse = "0.2.6" async-net = "2.0.0" bitfield-struct = "0.9.2" bytes = "1.9.0" diff --git a/gui/src/data/mod.rs b/gui/src/data/mod.rs index ddae43367..179a533bf 100644 --- a/gui/src/data/mod.rs +++ b/gui/src/data/mod.rs @@ -9,9 +9,12 @@ mod part; mod prof; /// Manages all files and directories that stored in the data root. +/// +/// This does not manage file content. Its job is to organize filesystem hierarchy. pub struct DataMgr { part: Part, prof: Prof, + logs: PathBuf, } impl DataMgr { @@ -20,6 +23,7 @@ impl DataMgr { let root: PathBuf = root.into(); let part = root.join("part"); let prof = root.join("prof"); + let logs = root.join("kernel.txt"); // Create top-level directories. Self::create_dir(&part)?; @@ -28,17 +32,22 @@ impl DataMgr { Ok(Self { part: Part::new(part), prof: Prof::new(prof), + logs, }) } - pub fn part(&self) -> &Part { + pub fn partitions(&self) -> &Part { &self.part } - pub fn prof(&self) -> &Prof { + pub fn profiles(&self) -> &Prof { &self.prof } + pub fn logs(&self) -> &Path { + &self.logs + } + fn create_dir(path: &Path) -> Result<(), DataError> { if let Err(e) = std::fs::create_dir(path) { if e.kind() != ErrorKind::AlreadyExists { diff --git a/gui/src/log/file.rs b/gui/src/log/file.rs new file mode 100644 index 000000000..27b8a3f76 --- /dev/null +++ b/gui/src/log/file.rs @@ -0,0 +1,33 @@ +use anstyle_parse::Perform; +use std::fs::File; +use std::io::{BufWriter, Write}; + +/// Implementation of [`Perform`] for [`File`]. +pub struct LogFile(BufWriter); + +impl LogFile { + pub fn new(file: File) -> Self { + Self(BufWriter::new(file)) + } +} + +impl Perform for LogFile { + fn print(&mut self, c: char) { + self.0 + .write_all(c.encode_utf8(&mut [0; 4]).as_bytes()) + .unwrap(); + } + + fn execute(&mut self, byte: u8) { + match byte { + b'\n' => { + #[cfg(unix)] + self.0.write_all(b"\n").unwrap(); + #[cfg(windows)] + self.0.write_all(b"\r\n").unwrap(); + self.0.flush().unwrap(); + } + _ => (), + } + } +} diff --git a/gui/src/log/mod.rs b/gui/src/log/mod.rs new file mode 100644 index 000000000..f14db3439 --- /dev/null +++ b/gui/src/log/mod.rs @@ -0,0 +1,40 @@ +use self::file::LogFile; +use anstyle_parse::Parser; +use obconf::ConsoleType; +use std::fs::File; +use std::io::{stderr, stdout, Write}; +use std::path::Path; + +mod file; + +/// Provides method to write kernel logs. +pub struct LogWriter { + file: LogFile, + parser: Parser, +} + +impl LogWriter { + pub fn new(file: &Path) -> Result { + let file = File::create(file)?; + + Ok(Self { + file: LogFile::new(file), + parser: Parser::default(), + }) + } + + pub fn write(&mut self, ty: ConsoleType, msg: String) { + // Write console. + let msg = msg.as_bytes(); + + match ty { + ConsoleType::Info => stdout().write_all(msg).unwrap(), + ConsoleType::Warn | ConsoleType::Error => stderr().write_all(msg).unwrap(), + } + + // Write file. + for &b in msg { + self.parser.advance(&mut self.file, b); + } + } +} diff --git a/gui/src/main.rs b/gui/src/main.rs index a77f2846c..6dcfe1ca1 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -2,6 +2,7 @@ use self::data::{DataError, DataMgr}; use self::graphics::{EngineBuilder, GraphicsError, PhysicalDevice}; +use self::log::LogWriter; use self::profile::{DisplayResolution, Profile}; use self::setup::{run_setup, SetupError}; use self::ui::{ @@ -13,11 +14,9 @@ use async_net::{TcpListener, TcpStream}; use clap::{Parser, ValueEnum}; use erdp::ErrorDisplay; use futures::{select_biased, AsyncReadExt, FutureExt}; -use obconf::ConsoleType; use slint::{ComponentHandle, ModelRc, SharedString, ToSharedString, VecModel, WindowHandle}; use std::cell::Cell; use std::future::Future; -use std::io::{stderr, stdout, Write}; use std::net::SocketAddrV4; use std::path::PathBuf; use std::pin::pin; @@ -34,6 +33,7 @@ mod dialogs; mod gdb; mod graphics; mod hv; +mod log; mod panic; mod profile; mod rt; @@ -178,7 +178,7 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> { // Load profiles. let mut profiles = Vec::new(); - for l in data.prof().list().map_err(ProgramError::ListProfile)? { + for l in data.profiles().list().map_err(ProgramError::ListProfile)? { let l = l.map_err(ProgramError::ListProfile)?; let p = Profile::load(&l).map_err(ProgramError::LoadProfile)?; @@ -189,7 +189,7 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> { if profiles.is_empty() { // Create directory. let p = Profile::default(); - let l = data.prof().data(p.id()); + let l = data.profiles().data(p.id()); if let Err(e) = std::fs::create_dir(&l) { return Err(ProgramError::CreateDirectory(l, e)); @@ -241,6 +241,9 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> { .with_title("Obliteration"); // Prepare to launch VMM. + let logs = data.logs(); + let mut logs = + LogWriter::new(logs).map_err(|e| ProgramError::CreateKernelLog(logs.into(), e))?; let shutdown = Arc::default(); let graphics = graphics .build(&profile, attrs, &shutdown) @@ -279,14 +282,7 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> { if let Some(vmm) = vmm { match vmm { VmmEvent::Exit(_, _) => todo!(), - VmmEvent::Log(t, m) => { - let m = m.as_bytes(); - - match t { - ConsoleType::Info => stdout().write_all(m).unwrap(), - ConsoleType::Warn | ConsoleType::Error => stderr().write_all(m).unwrap(), - } - } + VmmEvent::Log(t, m) => logs.write(t, m), } } @@ -332,7 +328,7 @@ async fn run_launcher( let win = win.unwrap(); let row = win.get_selected_profile(); let pro = profiles.update(row, &win); - let loc = data.prof().data(pro.id()); + let loc = data.profiles().data(pro.id()); // TODO: Display error instead of panic. pro.save(loc).unwrap(); @@ -527,6 +523,9 @@ enum ProgramError { #[error("couldn't show main window")] ShowMainWindow(#[source] slint::PlatformError), + #[error("couldn't create {0}")] + CreateKernelLog(PathBuf, #[source] std::io::Error), + #[error("couldn't build graphics engine")] BuildGraphicsEngine(#[source] GraphicsError), diff --git a/gui/src/setup/mod.rs b/gui/src/setup/mod.rs index 5873aad8f..936dc4746 100644 --- a/gui/src/setup/mod.rs +++ b/gui/src/setup/mod.rs @@ -30,7 +30,7 @@ pub async fn run_setup() -> Result, SetupError> { // Check if root partition exists. let mgr = DataMgr::new(p).map_err(|e| SetupError::DataManager(p.to_owned(), e))?; - if mgr.part().meta("md0").is_file() { + if mgr.partitions().meta("md0").is_file() { return Ok(Some(mgr)); } } @@ -178,7 +178,7 @@ fn set_data_root(win: SetupWizard) { return; } - win.invoke_set_data_root_ok(mgr.part().meta("md0").is_file()); + win.invoke_set_data_root_ok(mgr.partitions().meta("md0").is_file()); } async fn browse_firmware(win: SetupWizard) { @@ -352,7 +352,7 @@ fn extract_partition( }; // Create database file for file/directory metadata. - let mp = dmgr.part().meta(dev); + let mp = dmgr.partitions().meta(dev); let meta = match File::create_new(&mp) { Ok(v) => v, Err(e) => return Err(PartitionError::CreateFile(mp, e)), @@ -383,7 +383,7 @@ fn extract_partition( drop(tab); // Extract items. - let root = dmgr.part().data(dev); + let root = dmgr.partitions().data(dev); loop { // Get next item.