-
Jeremy Soller authoredJeremy Soller authored
installer_tui.rs 8.84 KiB
extern crate arg_parser;
extern crate redox_installer;
extern crate serde;
extern crate termion;
extern crate toml;
use redox_installer::{Config, with_redoxfs};
use std::{fs, io, process};
use termion::input::TermRead;
#[cfg(not(target_os = "redox"))]
fn disk_paths(_paths: &mut Vec<(String, u64)>) {}
#[cfg(target_os = "redox")]
fn disk_paths(paths: &mut Vec<(String, u64)>) {
let mut schemes = Vec::new();
match fs::read_dir(":") {
Ok(entries) => for entry_res in entries {
if let Ok(entry) = entry_res {
let path = entry.path();
if let Ok(path_str) = path.into_os_string().into_string() {
let scheme = path_str.trim_start_matches(':').trim_matches('/');
if scheme.starts_with("disk") {
schemes.push(format!("{}:", scheme));
}
}
}
},
Err(err) => {
eprintln!("installer_tui: failed to list schemes: {}", err);
}
}
for scheme in schemes {
let is_dir = fs::metadata(&scheme)
.map(|x| x.is_dir())
.unwrap_or(false);
if is_dir {
match fs::read_dir(&scheme) {
Ok(entries) => for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(path) = entry.path().into_os_string().into_string() {
if let Ok(metadata) = entry.metadata() {
let size = metadata.len();
if size > 0 {
paths.push((path, size));
}
}
}
}
},
Err(err) => {
eprintln!("installer_tui: failed to list '{}': {}", scheme, err);
}
}
}
}
}
const KB: u64 = 1024;
const MB: u64 = 1024 * KB;
const GB: u64 = 1024 * MB;
const TB: u64 = 1024 * GB;
fn format_size(size: u64) -> String {
if size >= TB {
format!("{} TB", size / TB)
} else if size >= GB {
format!("{} GB", size / GB)
} else if size >= MB {
format!("{} MB", size / MB)
} else if size >= KB {
format!("{} KB", size / KB)
} else {
format!("{} B", size)
}
}
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());
}
}
Ok(())
}
fn choose_disk() -> String {
let mut paths = Vec::new();
disk_paths(&mut paths);
loop {
for (i, (path, size)) in paths.iter().enumerate() {
eprintln!("\x1B[1m{}\x1B[0m: {}: {}", i + 1, path, format_size(*size));
}
if paths.is_empty() {
eprintln!("installer_tui: no drives found");
process::exit(1);
} else {
eprint!("Select a drive from 1 to {}: ", paths.len());
let mut line = String::new();
match io::stdin().read_line(&mut line) {
Ok(0) => {
eprintln!("installer_tui: failed to read line: end of input");
process::exit(1);
},
Ok(_) => (),
Err(err) => {
eprintln!("installer_tui: failed to read line: {}", err);
process::exit(1);
}
}
match line.trim().parse::<usize>() {
Ok(i) => {
if i >= 1 && i <= paths.len() {
break paths[i - 1].0.clone();
} else {
eprintln!("{} not from 1 to {}", i, paths.len());
}
},
Err(err) => {
eprintln!("invalid input: {}", err);
}
}
}
}
}
fn choose_password() -> Option<String> {
eprint!("installer_tui: redoxfs password (empty for none): ");
let password = io::stdin()
.read_passwd(&mut io::stderr())
.unwrap()
.unwrap_or(String::new());
eprintln!();
if password.is_empty() {
return None;
}
Some(password)
}
fn main() {
let disk_path = choose_disk();
let password_opt = choose_password();
let bootloader = {
let path = "file:/bootloader";
let mut bootloader = match fs::read(path) {
Ok(ok) => ok,
Err(err) => {
eprintln!("installer_tui: {}: failed to read: {}", path, err);
process::exit(1);
}
};
// Pad to 1MiB
while bootloader.len() < 1024 * 1024 {
bootloader.push(0);
}
bootloader
};
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) {
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))
);
}
}
},
Err(err) => {
eprintln!("installer_tui: {}: failed to read: {}", path, err);
return Err(failure::Error::from_boxed_compat(
Box::new(err))
);
}
}
};
// Copy bootloader, filesystem.toml, and kernel
let mut files = vec![
"bootloader".to_string(),
"filesystem.toml".to_string(),
"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))
);
}
}
// Packages are copied by previous code
config.packages.clear();
for (i, name) in files.iter().enumerate() {
eprintln!("copy {} [{}/{}]", name, i, files.len());
let src = format!("file:/{}", 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))
);
}
}
}
eprintln!("finished copying {} files", files.len());
let cookbook: Option<&'static str> = None;
redox_installer::install(config, mount_path, cookbook).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
err
)
})?;
eprintln!("finished installing, unmounting filesystem");
Ok(())
});
match res {
Ok(()) => {
eprintln!("installer_tui: installed successfully");
process::exit(0);
},
Err(err) => {
eprintln!("installer_tui: failed to install: {:?}", err);
process::exit(1);
}
}
}