diff --git a/src/bin/installer_tui.rs b/src/bin/installer_tui.rs index 2fe0862a0bf8d4bfb91f3f8111fa03e688bc9c1b..5c4acbee634a4eaa55d719f7cacde851900bc56a 100644 --- a/src/bin/installer_tui.rs +++ b/src/bin/installer_tui.rs @@ -1,11 +1,25 @@ extern crate arg_parser; +#[macro_use] extern crate failure; +extern crate pkgar; +extern crate pkgar_core; +extern crate pkgar_keys; extern crate redox_installer; extern crate serde; extern crate termion; extern crate toml; +use pkgar::{PackageHead, ext::EntryExt}; +use pkgar_core::PackageSrc; +use pkgar_keys::PublicKeyFile; use redox_installer::{Config, with_redoxfs}; -use std::{fs, io, process}; +use std::{ + ffi::OsStr, + fs, + io::{self, Read, Write}, + os::unix::fs::{MetadataExt, OpenOptionsExt, symlink}, + path::Path, + process +}; use termion::input::TermRead; #[cfg(not(target_os = "redox"))] @@ -76,24 +90,106 @@ fn format_size(size: u64) -> String { } } -fn dir_files(dir: &str, files: &mut Vec<String>) -> io::Result<()> { - for entry_res in fs::read_dir(&format!("file:/{}", dir))? { - let entry = entry_res?; - let path = entry.path(); - let path_str = path.into_os_string().into_string().map_err(|_| { - io::Error::new( - io::ErrorKind::InvalidData, - "Failed to convert Path to &str" - ) - })?; - let path_trimmed = path_str.trim_start_matches("file:/"); - let metadata = entry.metadata()?; - if metadata.is_dir() { - dir_files(path_trimmed, files)?; - } else { - files.push(path_trimmed.to_string()); +fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<(), failure::Error> { + if let Some(parent) = dest.parent() { + match fs::create_dir_all(&parent) { + Ok(()) => (), + Err(err) => { + return Err(format_err!("failed to create directory {}: {}", parent.display(), err)); + } } } + + let metadata = match fs::symlink_metadata(&src) { + Ok(ok) => ok, + Err(err) => { + return Err(format_err!("failed to read metadata of {}: {}", src.display(), err)); + }, + }; + + if metadata.file_type().is_symlink() { + let real_src = match fs::read_link(&src) { + Ok(ok) => ok, + Err(err) => { + return Err(format_err!("failed to read link {}: {}", src.display(), err)); + } + }; + + match symlink(&real_src, &dest) { + Ok(()) => (), + Err(err) => { + return Err(format_err!("failed to copy link {} ({}) to {}: {}", src.display(), real_src.display(), dest.display(), err)); + }, + } + } else { + let mut src_file = match fs::File::open(&src) { + Ok(ok) => ok, + Err(err) => { + return Err(format_err!("failed to open file {}: {}", src.display(), err)); + } + }; + + let mut dest_file = match fs::OpenOptions::new().write(true).create_new(true).mode(metadata.mode()).open(&dest) { + Ok(ok) => ok, + Err(err) => { + return Err(format_err!("failed to create file {}: {}", dest.display(), err)); + } + }; + + loop { + let count = match src_file.read(buf) { + Ok(ok) => ok, + Err(err) => { + return Err(format_err!("failed to read file {}: {}", src.display(), err)); + } + }; + + if count == 0 { + break; + } + + match dest_file.write_all(&buf[..count]) { + Ok(()) => (), + Err(err) => { + return Err(format_err!("failed to write file {}: {}", dest.display(), err)); + } + } + } + } + + Ok(()) +} + +fn package_files(root_path: &Path, config: &mut Config, files: &mut Vec<String>) -> Result<(), pkgar::Error> { + //TODO: Remove packages from config where all files are located (and have valid shasum?) + config.packages.clear(); + + let pkey_path = "pkg/id_ed25519.pub.toml"; + let pkey = PublicKeyFile::open(&root_path.join(pkey_path))?.pkey; + files.push(pkey_path.to_string()); + + for item_res in fs::read_dir(&root_path.join("pkg"))? { + let item = item_res?; + let pkg_path = item.path(); + if pkg_path.extension() == Some(OsStr::new("pkgar_head")) { + let mut pkg = PackageHead::new(&pkg_path, &root_path, &pkey)?; + for entry in pkg.read_entries()? { + files.push( + entry + .check_path()? + .to_str().unwrap() + .to_string() + ); + } + files.push( + pkg_path + .strip_prefix(root_path).unwrap() + .to_str().unwrap() + .to_string() + ); + } + } + Ok(()) } @@ -158,16 +254,18 @@ fn choose_password() -> Option<String> { } fn main() { + let root_path = Path::new("file:"); + let disk_path = choose_disk(); let password_opt = choose_password(); let bootloader = { - let path = "file:/bootloader"; - let mut bootloader = match fs::read(path) { + let path = root_path.join("bootloader"); + let mut bootloader = match fs::read(&path) { Ok(ok) => ok, Err(err) => { - eprintln!("installer_tui: {}: failed to read: {}", path, err); + eprintln!("installer_tui: {}: failed to read: {}", path.display(), err); process::exit(1); } }; @@ -182,26 +280,20 @@ fn main() { let res = with_redoxfs(&disk_path, password_opt.as_ref().map(|x| x.as_bytes()), &bootloader, |mount_path| -> Result<(), failure::Error> { let mut config: Config = { - let path = "file:/filesystem.toml"; - match fs::read_to_string(path) { + let path = root_path.join("filesystem.toml"); + match fs::read_to_string(&path) { Ok(config_data) => { match toml::from_str(&config_data) { Ok(config) => { config }, Err(err) => { - eprintln!("installer_tui: {}: failed to decode: {}", path, err); - return Err(failure::Error::from_boxed_compat( - Box::new(err)) - ); + return Err(format_err!("{}: failed to decode: {}", path.display(), err)); } } }, Err(err) => { - eprintln!("installer_tui: {}: failed to read: {}", path, err); - return Err(failure::Error::from_boxed_compat( - Box::new(err)) - ); + return Err(format_err!("{}: failed to read: {}", path.display(), err)); } } }; @@ -213,55 +305,22 @@ fn main() { "kernel".to_string() ]; - // Copy files from known directories - //TODO: Use package data - for dir in [ - "bin", - "etc", - "include", - "lib", - "pkg", - "share", - "ssl", - "ui" - ].iter() { - if let Err(err) = dir_files(dir, &mut files) { - eprintln!("installer_tui: failed to read files from {}: {}", dir, err); - return Err(failure::Error::from_boxed_compat( - Box::new(err)) - ); - } + // Copy files from locally installed packages + if let Err(err) = package_files(&root_path, &mut config, &mut files) { + return Err(format_err!("failed to read package files: {}", err)); } - // Packages are copied by previous code - config.packages.clear(); + // Sort and remove duplicates + files.sort(); + files.dedup(); + let mut buf = vec![0; 4 * 1024 * 1024]; for (i, name) in files.iter().enumerate() { eprintln!("copy {} [{}/{}]", name, i, files.len()); - let src = format!("file:/{}", name); + let src = root_path.join(name); let dest = mount_path.join(name); - if let Some(parent) = dest.parent() { - match fs::create_dir_all(&parent) { - Ok(()) => (), - Err(err) => { - eprintln!("installer_tui: failed to create directory {}: {}", parent.display(), err); - return Err(failure::Error::from_boxed_compat( - Box::new(err)) - ); - } - } - } - - match fs::copy(&src, &dest) { - Ok(_) => (), - Err(err) => { - eprintln!("installer_tui: failed to copy file {} to {}: {}", src, dest.display(), err); - return Err(failure::Error::from_boxed_compat( - Box::new(err)) - ); - } - } + copy_file(&src, &dest, &mut buf)?; } eprintln!("finished copying {} files", files.len()); @@ -285,7 +344,7 @@ fn main() { process::exit(0); }, Err(err) => { - eprintln!("installer_tui: failed to install: {:?}", err); + eprintln!("installer_tui: failed to install: {}", err); process::exit(1); } }