0.2.9: add tests and make it possible to install to disk

This commit is contained in:
Jeremy Soller
2022-03-17 08:50:01 -06:00
parent 0c731043ac
commit 3c03576518
12 changed files with 386 additions and 94 deletions
+1
View File
@@ -1,3 +1,4 @@
pkg
sysroot
/target/
/test.bin
Generated
+2 -1
View File
@@ -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",
+2 -1
View File
@@ -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 <jackpot51@gmail.com>"]
@@ -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"
+14 -74
View File
@@ -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<P, T, F>(disk_path: &P, password_opt: Option<&[u8]>, bootloader: &[u8], callback: F)
-> T where
P: AsRef<Path>,
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<String>) -> 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<String>) -> 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(
+1 -1
View File
@@ -11,7 +11,7 @@ use std::path::Path;
//type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Default, Deserialize)]
#[derive(Clone, Debug, Default, Deserialize)]
pub struct FileConfig {
pub path: String,
pub data: String,
+1 -1
View File
@@ -1,4 +1,4 @@
#[derive(Debug, Default, Deserialize)]
#[derive(Clone, Debug, Default, Deserialize)]
pub struct GeneralConfig {
pub prompt: bool
}
+5 -5
View File
@@ -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)]
+1 -1
View File
@@ -1,4 +1,4 @@
#[derive(Debug, Default, Deserialize)]
#[derive(Clone, Debug, Default, Deserialize)]
pub struct PackageConfig {
pub version: Option<String>,
pub git: Option<String>,
+1 -1
View File
@@ -1,4 +1,4 @@
#[derive(Debug, Default, Deserialize)]
#[derive(Clone, Debug, Default, Deserialize)]
pub struct UserConfig {
pub password: Option<String>,
pub uid: Option<u32>,
+144 -9
View File
@@ -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<T> = std::result::Result<T, Error>;
@@ -41,6 +51,10 @@ fn hash_password(password: &str) -> Result<String> {
}
}
fn syscall_error(err: syscall::Error) -> io::Error {
io::Error::from_raw_os_error(err.errno)
}
fn unwrap_or_prompt<T: FromStr>(option: Option<T>, context: &mut liner::Context, prompt: &str) -> Result<T> {
match option {
Some(t) => Ok(t),
@@ -76,6 +90,7 @@ fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result<String> {
}
}
//TODO: error handling
fn install_packages<S: AsRef<str>>(config: &Config, dest: &str, cookbook: Option<S>) {
let target = &env::var("TARGET").unwrap_or(
option_env!("TARGET").map_or(
@@ -126,7 +141,7 @@ fn install_packages<S: AsRef<str>>(config: &Config, dest: &str, cookbook: Option
}
}
pub fn install<P: AsRef<Path>, S: AsRef<str>>(config: Config, output_dir: P, cookbook: Option<S>) -> Result<()> {
pub fn install_dir<P: AsRef<Path>, S: AsRef<str>>(config: Config, output_dir: P, cookbook: Option<S>) -> Result<()> {
//let mut context = liner::Context::new();
macro_rules! prompt {
@@ -150,9 +165,6 @@ pub fn install<P: AsRef<Path>, S: AsRef<str>>(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<P: AsRef<Path>, S: AsRef<str>>(config: Config, output_dir: P, coo
Ok(())
}
pub fn with_redoxfs<P, T, F>(disk_path: P, password_opt: Option<&[u8]>, bootloader: &[u8], callback: F)
-> Result<T> where
P: AsRef<Path>,
F: FnOnce(&Path) -> Result<T>
{
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<P, S>(config: Config, output: P, cookbook: Option<S>)
-> Result<()> where
P: AsRef<Path>,
S: AsRef<str>,
{
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())
})
}
}
Executable
+29
View File
@@ -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"
+185
View File
@@ -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