0.2.9: add tests and make it possible to install to disk
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
pkg
|
||||
sysroot
|
||||
/target/
|
||||
/test.bin
|
||||
|
||||
Generated
+2
-1
@@ -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
@@ -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
@@ -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
@@ -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,4 +1,4 @@
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
pub struct GeneralConfig {
|
||||
pub prompt: bool
|
||||
}
|
||||
|
||||
+5
-5
@@ -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,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
@@ -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
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user