diff --git a/.gitignore b/.gitignore index d7dac7a264..787e2cfeca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ pkg sysroot /target/ +/test.bin diff --git a/Cargo.lock b/Cargo.lock index d19b0dd508..d8fbb23e85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,7 +950,7 @@ dependencies = [ [[package]] name = "redox_installer" -version = "0.2.8" +version = "0.2.9" dependencies = [ "arg_parser", "failure", @@ -959,6 +959,7 @@ dependencies = [ "rand 0.8.5", "redox_liner", "redox_pkgutils", + "redox_syscall", "redoxfs", "rust-argon2", "serde", diff --git a/Cargo.toml b/Cargo.toml index 9aa147ee6c..c6dbd0afc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "redox_installer" -version = "0.2.8" +version = "0.2.9" description = "A Redox filesystem builder" license = "MIT" authors = ["Jeremy Soller "] @@ -27,6 +27,7 @@ failure = "0.1.8" pkgar = "0.1.6" rand = "0.8" redox_pkgutils = "0.1.6" +redox_syscall = "0.2.11" redoxfs = "0.5.0" rust-argon2 = "0.8.2" serde = "1.0.110" diff --git a/src/bin/installer_tui.rs b/src/bin/installer_tui.rs index 30c69b8fcb..2fe0862a0b 100644 --- a/src/bin/installer_tui.rs +++ b/src/bin/installer_tui.rs @@ -1,18 +1,11 @@ extern crate arg_parser; extern crate redox_installer; -extern crate redoxfs; extern crate serde; extern crate termion; extern crate toml; -use redox_installer::Config; -use redoxfs::{DiskFile, FileSystem}; -use std::{fs, io, process, sync, thread, time}; -use std::ffi::OsStr; -use std::io::{Read, Write}; -use std::ops::DerefMut; -use std::path::Path; -use std::process::Command; +use redox_installer::{Config, with_redoxfs}; +use std::{fs, io, process}; use termion::input::TermRead; #[cfg(not(target_os = "redox"))] @@ -83,67 +76,6 @@ fn format_size(size: u64) -> String { } } -fn with_redoxfs(disk_path: &P, password_opt: Option<&[u8]>, bootloader: &[u8], callback: F) - -> T where - P: AsRef, - T: Send + Sync + 'static, - F: FnMut(&Path) -> T + Send + Sync + 'static -{ - let mount_path = "file/installer_tui"; - - let res = { - let disk = DiskFile::open(disk_path).unwrap(); - - if cfg!(not(target_os = "redox")) { - if ! Path::new(mount_path).exists() { - fs::create_dir(mount_path).unwrap(); - } - } - - let ctime = time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap(); - let fs = FileSystem::create_reserved(disk, password_opt, bootloader, ctime.as_secs(), ctime.subsec_nanos()).unwrap(); - - let callback_mutex = sync::Arc::new(sync::Mutex::new(callback)); - let join_handle = redoxfs::mount(fs, mount_path, move |real_path| { - let callback_mutex = callback_mutex.clone(); - let real_path = real_path.to_owned(); - thread::spawn(move || { - let res = { - let mut callback_guard = callback_mutex.lock().unwrap(); - let callback = callback_guard.deref_mut(); - callback(&real_path) - }; - - if cfg!(target_os = "redox") { - fs::remove_file(format!(":{}", mount_path)).unwrap(); - } else { - let status_res = if cfg!(target_os = "linux") { - Command::new("fusermount") - .arg("-u") - .arg(mount_path) - .status() - } else { - Command::new("umount") - .arg(mount_path) - .status() - }; - - let status = status_res.unwrap(); - if ! status.success() { - panic!("umount failed"); - } - } - - res - }) - }).unwrap(); - - join_handle.join().unwrap() - }; - - res -} - fn dir_files(dir: &str, files: &mut Vec) -> io::Result<()> { for entry_res in fs::read_dir(&format!("file:/{}", dir))? { let entry = entry_res?; @@ -157,7 +89,7 @@ fn dir_files(dir: &str, files: &mut Vec) -> io::Result<()> { let path_trimmed = path_str.trim_start_matches("file:/"); let metadata = entry.metadata()?; if metadata.is_dir() { - dir_files(path_trimmed, files); + dir_files(path_trimmed, files)?; } else { files.push(path_trimmed.to_string()); } @@ -283,7 +215,16 @@ fn main() { // Copy files from known directories //TODO: Use package data - for dir in ["bin", "etc", "include", "lib", "pkg", "share", "ssl", "ui"].iter() { + 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( @@ -295,7 +236,6 @@ fn main() { // Packages are copied by previous code config.packages.clear(); - let mut buf = vec![0; 4 * 1024 * 1024]; for (i, name) in files.iter().enumerate() { eprintln!("copy {} [{}/{}]", name, i, files.len()); @@ -314,7 +254,7 @@ fn main() { } match fs::copy(&src, &dest) { - Ok(ok) => (), + Ok(_) => (), Err(err) => { eprintln!("installer_tui: failed to copy file {} to {}: {}", src, dest.display(), err); return Err(failure::Error::from_boxed_compat( diff --git a/src/config/file.rs b/src/config/file.rs index 640b47bee7..cccfbd782f 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -11,7 +11,7 @@ use std::path::Path; //type Result = std::result::Result; -#[derive(Debug, Default, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct FileConfig { pub path: String, pub data: String, diff --git a/src/config/general.rs b/src/config/general.rs index f247293663..95704c6515 100644 --- a/src/config/general.rs +++ b/src/config/general.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Default, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct GeneralConfig { pub prompt: bool } diff --git a/src/config/mod.rs b/src/config/mod.rs index 98a4a68f4b..6172d4a86d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,11 +1,11 @@ use std::collections::BTreeMap; -mod general; -pub(crate) mod file; -mod package; -mod user; +pub mod general; +pub mod file; +pub mod package; +pub mod user; -#[derive(Debug, Default, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct Config { pub general: general::GeneralConfig, #[serde(default)] diff --git a/src/config/package.rs b/src/config/package.rs index 005ca3acf0..8bb5e803e8 100644 --- a/src/config/package.rs +++ b/src/config/package.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Default, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct PackageConfig { pub version: Option, pub git: Option, diff --git a/src/config/user.rs b/src/config/user.rs index 96113c68fa..4886f4f968 100644 --- a/src/config/user.rs +++ b/src/config/user.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Default, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct UserConfig { pub password: Option, pub uid: Option, diff --git a/src/lib.rs b/src/lib.rs index 268564dbda..acc759f1af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,23 +6,33 @@ extern crate liner; extern crate failure; extern crate pkgutils; extern crate rand; +extern crate redoxfs; +extern crate syscall; extern crate termion; mod config; pub use config::Config; use config::file::FileConfig; +use config::package::PackageConfig; use failure::{Error, err_msg}; use rand::{RngCore, rngs::OsRng}; +use redoxfs::{DiskFile, FileSystem}; use termion::input::TermRead; use pkgutils::{Repo, Package}; -use std::env; -use std::io::{self, stderr, Write}; -use std::path::Path; -use std::process::{self, Command}; -use std::str::FromStr; +use std::{ + env, + fs, + io::{self, stderr, Write}, + path::Path, + process::{self, Command}, + str::FromStr, + sync::mpsc::channel, + time::{SystemTime, UNIX_EPOCH}, + thread, +}; pub(crate) type Result = std::result::Result; @@ -41,6 +51,10 @@ fn hash_password(password: &str) -> Result { } } +fn syscall_error(err: syscall::Error) -> io::Error { + io::Error::from_raw_os_error(err.errno) +} + fn unwrap_or_prompt(option: Option, context: &mut liner::Context, prompt: &str) -> Result { match option { Some(t) => Ok(t), @@ -76,6 +90,7 @@ fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result { } } +//TODO: error handling fn install_packages>(config: &Config, dest: &str, cookbook: Option) { let target = &env::var("TARGET").unwrap_or( option_env!("TARGET").map_or( @@ -126,7 +141,7 @@ fn install_packages>(config: &Config, dest: &str, cookbook: Option } } -pub fn install, S: AsRef>(config: Config, output_dir: P, cookbook: Option) -> Result<()> { +pub fn install_dir, S: AsRef>(config: Config, output_dir: P, cookbook: Option) -> Result<()> { //let mut context = liner::Context::new(); macro_rules! prompt { @@ -150,9 +165,6 @@ pub fn install, S: AsRef>(config: Config, output_dir: P, coo let output_dir = output_dir.as_ref(); - println!("Install {:#?} to {}", config, output_dir.display()); - - // TODO: Mount disk if output is a file let output_dir = output_dir.to_owned(); install_packages(&config, output_dir.to_str().unwrap(), cookbook); @@ -240,3 +252,126 @@ pub fn install, S: AsRef>(config: Config, output_dir: P, coo Ok(()) } + +pub fn with_redoxfs(disk_path: P, password_opt: Option<&[u8]>, bootloader: &[u8], callback: F) + -> Result where + P: AsRef, + F: FnOnce(&Path) -> Result +{ + let mount_path = if cfg!(target_os = "redox") { + "file/redox_installer" + } else { + "/tmp/redox_installer" + }; + + let disk = DiskFile::open(disk_path).map_err(syscall_error)?; + + if cfg!(not(target_os = "redox")) { + if ! Path::new(mount_path).exists() { + fs::create_dir(mount_path)?; + } + } + + let ctime = SystemTime::now().duration_since(UNIX_EPOCH)?; + let fs = FileSystem::create_reserved( + disk, + password_opt, + bootloader, + ctime.as_secs(), + ctime.subsec_nanos() + ).map_err(syscall_error)?; + + let (tx, rx) = channel(); + let join_handle = thread::spawn(move || { + let res = redoxfs::mount( + fs, + mount_path, + |real_path| { + tx.send(Ok(real_path.to_owned())).unwrap(); + } + ); + match res { + Ok(()) => (), + Err(err) => { + tx.send(Err(err)).unwrap(); + }, + }; + }); + + let res = match rx.recv() { + Ok(ok) => match ok { + Ok(real_path) => callback(&real_path), + Err(err) => return Err(err.into()), + }, + Err(_) => return Err(io::Error::new( + io::ErrorKind::NotConnected, + "redoxfs thread did not send a result" + ).into()), + }; + + if cfg!(target_os = "redox") { + fs::remove_file(format!(":{}", mount_path))?; + } else { + let status_res = if cfg!(target_os = "linux") { + Command::new("fusermount") + .arg("-u") + .arg(mount_path) + .status() + } else { + Command::new("umount") + .arg(mount_path) + .status() + }; + + let status = status_res?; + if ! status.success() { + return Err(io::Error::new( + io::ErrorKind::Other, + "redoxfs umount failed" + ).into()); + } + } + + join_handle.join().unwrap(); + + res +} + +pub fn install(config: Config, output: P, cookbook: Option) + -> Result<()> where + P: AsRef, + S: AsRef, +{ + println!("Install {:#?} to {}", config, output.as_ref().display()); + + if output.as_ref().is_dir() { + install_dir(config, output, cookbook) + } else { + let bootloader = { + //TODO: make it safe to + let bootloader_dir = "/tmp/redox_installer_bootloader"; + if Path::new(bootloader_dir).exists() { + fs::remove_dir_all(&bootloader_dir)?; + } + + fs::create_dir(bootloader_dir)?; + + let mut bootloader_config = Config::default(); + bootloader_config.packages.insert("bootloader".to_string(), PackageConfig::default()); + install_packages(&bootloader_config, bootloader_dir, cookbook.as_ref()); + + let mut bootloader = fs::read(Path::new(bootloader_dir).join("bootloader"))?; + + // Pad to 1 MiB + while bootloader.len() < 1024 * 1024 { + bootloader.push(0); + } + + bootloader + }; + + with_redoxfs(output, None, &bootloader, move |mount_path| -> Result<()> { + install_dir(config, mount_path, cookbook.as_ref()) + }) + } +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000000..8615d39efa --- /dev/null +++ b/test.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +IMAGE=test.bin + +QEMU_ARGS=( + -cpu max + -machine q35 + -m 2048 + -smp 4 + -serial mon:stdio + -netdev user,id=net0 + -device e1000,netdev=net0 +) + +if [ -e /dev/kvm ] +then + QEMU_ARGS+=(-accel kvm) +fi + +set -ex + +cargo build --release + +rm -f "${IMAGE}" +fallocate -l 1GiB "${IMAGE}" + +target/release/redox_installer -c test.toml "${IMAGE}" + +qemu-system-x86_64 "${QEMU_ARGS[@]}" -drive "file=${IMAGE},format=raw" diff --git a/test.toml b/test.toml new file mode 100644 index 0000000000..02376f0154 --- /dev/null +++ b/test.toml @@ -0,0 +1,185 @@ +# This is the default configuration file + +# General settings +[general] +# Do not prompt if settings are not defined +prompt = false + +# Package settings +[packages] +audiod = {} +bootloader = {} +ca-certificates = {} +contain = {} +coreutils = {} +dash = {} +diffutils = {} +drivers = {} +extrautils = {} +findutils = {} +gdbserver = {} +gnu-make = {} +installer = {} +ion = {} +ipcd = {} +kernel = {} +netdb = {} +netstack = {} +netsurf = {} +netutils = {} +orbdata = {} +orbital = {} +orbterm = {} +orbutils = {} +pkgutils = {} +ptyd = {} +redoxfs = {} +relibc = {} +resist = {} +smith = {} +strace = {} +userutils = {} +uutils = {} +vim = {} + +# User settings +[users.root] +password = "password" +uid = 0 +gid = 0 +name = "root" +home = "/root" + +[users.user] +# Password is unset +password = "" + +[[files]] +path = "/etc/init.d/00_base" +data = """ +ipcd +ptyd +pcid /etc/pcid.d/ +""" + +[[files]] +path = "/etc/init.d/10_net" +data = """ +smolnetd +dnsd +dhcpd -b +""" + +[[files]] +path = "/etc/init.d/20_orbital" +data = """ +audiod +orbital display:3/activate orblogin launcher +""" + +[[files]] +path = "/etc/init.d/30_console" +data = """ +getty display:2 +getty debug: -J +""" + +[[files]] +path = "/etc/net/dns" +data = """ +208.67.222.222 +""" + +[[files]] +path = "/etc/net/ip" +data = """ +10.0.2.15 +""" + +[[files]] +path = "/etc/net/ip_router" +data = """ +10.0.2.2 +""" + +[[files]] +path = "/etc/net/ip_subnet" +data = """ +255.255.255.0 +""" + +[[files]] +path = "/etc/net/mac" +data = """ +54-52-00-ab-cd-ef +""" + +[[files]] +path = "/etc/pkg.d/50_redox" +data = "https://static.redox-os.org/pkg" + +[[files]] +path = "/etc/group" +data = """ +root;0;root +user;1000;user +sudo;1;user +""" + +[[files]] +path = "/etc/hostname" +data = """ +redox +""" + +[[files]] +path = "/usr/bin" +data = "../bin" +symlink = true + +[[files]] +path = "/usr/games" +data = "../games" +symlink = true + +[[files]] +path = "/usr/include" +data = "../include" +symlink = true + +[[files]] +path = "/usr/lib" +data = "../lib" +symlink = true + +[[files]] +path = "/usr/share" +data = "../share" +symlink = true + +[[files]] +path = "/tmp" +data = "" +directory= true +# 0o1777 +mode = 1023 + +[[files]] +path = "/dev/null" +data = "null:" +symlink = true + +[[files]] +path = "/dev/random" +data = "rand:" +symlink = true + +[[files]] +path = "/dev/urandom" +data = "rand:" +symlink = true + +[[files]] +path = "/dev/zero" +data = "zero:" +symlink = true