Merge branch 'split-cargo-features' into 'master'
Split installer code to cargo feature See merge request redox-os/installer!60
This commit is contained in:
Generated
+10
-50
@@ -317,12 +317,6 @@ version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.23.2"
|
||||
@@ -357,9 +351,11 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.22"
|
||||
source = "git+https://github.com/tea/cc-rs?branch=riscv-abi-arch-fix#588ceacb084af41415690c57688e338a32a1f1b4"
|
||||
version = "1.2.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@@ -718,6 +714,12 @@ version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1224,15 +1226,6 @@ version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@@ -1708,7 +1701,6 @@ version = "0.2.37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arg_parser",
|
||||
"cc",
|
||||
"fatfs",
|
||||
"fscommon",
|
||||
"gpt",
|
||||
@@ -1719,7 +1711,6 @@ dependencies = [
|
||||
"pkgar-keys",
|
||||
"rand",
|
||||
"redox-pkg",
|
||||
"redox_liner",
|
||||
"redox_syscall",
|
||||
"redoxfs",
|
||||
"rust-argon2",
|
||||
@@ -1730,19 +1721,6 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_liner"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee4596948a78a2ac29268d4ee45f788789ac8c032c77cf255378e5c96ca0c779"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"itertools",
|
||||
"strip-ansi-escapes",
|
||||
"termion",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.17"
|
||||
@@ -2118,15 +2096,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "strip-ansi-escapes"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025"
|
||||
dependencies = [
|
||||
"vte",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
@@ -2503,15 +2472,6 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
||||
+39
-21
@@ -11,42 +11,60 @@ edition = "2021"
|
||||
[[bin]]
|
||||
name = "redox_installer"
|
||||
path = "src/bin/installer.rs"
|
||||
required-features = ["installer"]
|
||||
|
||||
[[bin]]
|
||||
name = "redox_installer_tui"
|
||||
path = "src/bin/installer_tui.rs"
|
||||
required-features = ["installer"]
|
||||
|
||||
[lib]
|
||||
name = "redox_installer"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.89"
|
||||
arg_parser = "0.1.0"
|
||||
cc = "1"
|
||||
fatfs = "0.3.0"
|
||||
fscommon = "0.1.1"
|
||||
gpt = "3.0.0"
|
||||
libc = "0.2.70"
|
||||
pkgar = "0.1.19"
|
||||
pkgar-core = "0.1.19"
|
||||
pkgar-keys = "0.1.19"
|
||||
rand = "0.9"
|
||||
redox_liner = "0.5"
|
||||
redox-pkg = { version = "0.2.8", features = ["indicatif"] }
|
||||
redox_syscall = "0.5.2"
|
||||
redoxfs = "0.8.0"
|
||||
rust-argon2 = "0.8.2"
|
||||
anyhow = { version = "1" }
|
||||
arg_parser = { version = "0.1.0", optional = true }
|
||||
fatfs = { version = "0.3.0", optional = true }
|
||||
fscommon = { version = "0.1.1", optional = true }
|
||||
gpt = { version = "3.0.0", optional = true }
|
||||
libc = { version = "0.2.70" }
|
||||
pkgar = { version = "0.1.19", optional = true }
|
||||
pkgar-core = { version = "0.1.19", optional = true }
|
||||
pkgar-keys = { version = "0.1.19", optional = true }
|
||||
rand = { version = "0.9", optional = true }
|
||||
redox-pkg = { version = "0.2.8", features = ["indicatif"], optional = true }
|
||||
redox_syscall = { version = "0.5.2", optional = true }
|
||||
redoxfs = { version = "0.8.0", optional = true }
|
||||
rust-argon2 = { version = "0.8.2", optional = true }
|
||||
serde = "1"
|
||||
serde_derive = "1.0.110"
|
||||
termion = "4"
|
||||
toml = "0.8"
|
||||
uuid = { version = "1.4", features = ["v4"] }
|
||||
serde_derive = "1.0"
|
||||
termion = { version = "4", optional = true }
|
||||
toml = { version = "0.8" }
|
||||
uuid = { version = "1.4", features = ["v4"], optional = true }
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
libredox = "0.1"
|
||||
|
||||
[features]
|
||||
default = ["installer"]
|
||||
installer = [
|
||||
"arg_parser",
|
||||
"fatfs",
|
||||
"fscommon",
|
||||
"gpt",
|
||||
"pkgar",
|
||||
"pkgar-core",
|
||||
"pkgar-keys",
|
||||
"rand",
|
||||
"redox-pkg",
|
||||
"redox_syscall",
|
||||
"redoxfs",
|
||||
"rust-argon2",
|
||||
"termion",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
cc-11 = { git = "https://github.com/tea/cc-rs", branch="riscv-abi-arch-fix", package = "cc" }
|
||||
# https://github.com/briansmith/ring/issues/1999
|
||||
ring = { git = "https://gitlab.redox-os.org/redox-os/ring.git", branch = "redox-0.17.8" }
|
||||
|
||||
@@ -1,33 +1,3 @@
|
||||
use crate::Result;
|
||||
use libc::{gid_t, uid_t};
|
||||
|
||||
use std::ffi::{CString, OsStr};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Error, Write};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::{symlink, PermissionsExt};
|
||||
use std::path::Path;
|
||||
|
||||
//type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
fn chown<P: AsRef<Path>>(path: P, uid: uid_t, gid: gid_t, recursive: bool) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let c_path = CString::new(path.as_os_str().as_bytes()).unwrap();
|
||||
if unsafe { libc::chown(c_path.as_ptr(), uid, gid) } != 0 {
|
||||
return Err(Error::last_os_error().into());
|
||||
}
|
||||
|
||||
if recursive && path.is_dir() {
|
||||
for entry_res in fs::read_dir(path)? {
|
||||
let entry = entry_res?;
|
||||
chown(entry.path(), uid, gid, recursive)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct FileConfig {
|
||||
pub path: String,
|
||||
@@ -45,7 +15,6 @@ pub struct FileConfig {
|
||||
pub postinstall: bool,
|
||||
}
|
||||
|
||||
// TODO: Rewrite impls
|
||||
impl FileConfig {
|
||||
pub fn new_file(path: String, data: String) -> FileConfig {
|
||||
FileConfig {
|
||||
@@ -76,46 +45,4 @@ impl FileConfig {
|
||||
self.recursive_chown = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn create<P: AsRef<Path>>(&self, prefix: P) -> Result<()> {
|
||||
let path = self.path.trim_start_matches('/');
|
||||
let target_file = prefix.as_ref().join(path);
|
||||
|
||||
if self.directory {
|
||||
println!("Create directory {}", target_file.display());
|
||||
fs::create_dir_all(&target_file)?;
|
||||
self.apply_perms(&target_file)?;
|
||||
return Ok(());
|
||||
} else if let Some(parent) = target_file.parent() {
|
||||
println!("Create file parent {}", parent.display());
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
if self.symlink {
|
||||
println!("Create symlink {}", target_file.display());
|
||||
symlink(&OsStr::new(&self.data), &target_file)?;
|
||||
Ok(())
|
||||
} else {
|
||||
println!("Create file {}", target_file.display());
|
||||
let mut file = File::create(&target_file)?;
|
||||
file.write_all(self.data.as_bytes())?;
|
||||
|
||||
self.apply_perms(target_file)
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_perms<P: AsRef<Path>>(&self, target: P) -> Result<()> {
|
||||
let path = target.as_ref();
|
||||
let mode = self
|
||||
.mode
|
||||
.unwrap_or_else(|| if self.directory { 0o0755 } else { 0o0644 });
|
||||
let uid = self.uid.unwrap_or(!0);
|
||||
let gid = self.gid.unwrap_or(!0);
|
||||
|
||||
// chmod
|
||||
fs::set_permissions(path, fs::Permissions::from_mode(mode))?;
|
||||
|
||||
// chown
|
||||
chown(path, uid, gid, self.recursive_chown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
use anyhow::Result;
|
||||
use libc::{gid_t, uid_t};
|
||||
|
||||
use std::ffi::{CString, OsStr};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Error, Write};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::{symlink, PermissionsExt};
|
||||
use std::path::Path;
|
||||
|
||||
fn chown<P: AsRef<Path>>(path: P, uid: uid_t, gid: gid_t, recursive: bool) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let c_path = CString::new(path.as_os_str().as_bytes()).unwrap();
|
||||
if unsafe { libc::chown(c_path.as_ptr(), uid, gid) } != 0 {
|
||||
return Err(Error::last_os_error().into());
|
||||
}
|
||||
|
||||
if recursive && path.is_dir() {
|
||||
for entry_res in fs::read_dir(path)? {
|
||||
let entry = entry_res?;
|
||||
chown(entry.path(), uid, gid, recursive)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Rewrite impls
|
||||
impl crate::FileConfig {
|
||||
pub(crate) fn create<P: AsRef<Path>>(&self, prefix: P) -> Result<()> {
|
||||
let path = self.path.trim_start_matches('/');
|
||||
let target_file = prefix.as_ref().join(path);
|
||||
|
||||
if self.directory {
|
||||
println!("Create directory {}", target_file.display());
|
||||
fs::create_dir_all(&target_file)?;
|
||||
self.apply_perms(&target_file)?;
|
||||
return Ok(());
|
||||
} else if let Some(parent) = target_file.parent() {
|
||||
println!("Create file parent {}", parent.display());
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
if self.symlink {
|
||||
println!("Create symlink {}", target_file.display());
|
||||
symlink(&OsStr::new(&self.data), &target_file)?;
|
||||
Ok(())
|
||||
} else {
|
||||
println!("Create file {}", target_file.display());
|
||||
let mut file = File::create(&target_file)?;
|
||||
file.write_all(self.data.as_bytes())?;
|
||||
|
||||
self.apply_perms(target_file)
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_perms<P: AsRef<Path>>(&self, target: P) -> Result<()> {
|
||||
let path = target.as_ref();
|
||||
let mode = self
|
||||
.mode
|
||||
.unwrap_or_else(|| if self.directory { 0o0755 } else { 0o0644 });
|
||||
let uid = self.uid.unwrap_or(!0);
|
||||
let gid = self.gid.unwrap_or(!0);
|
||||
|
||||
// chmod
|
||||
fs::set_permissions(path, fs::Permissions::from_mode(mode))?;
|
||||
|
||||
// chown
|
||||
chown(path, uid, gid, self.recursive_chown)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ use anyhow::bail;
|
||||
use anyhow::Result;
|
||||
|
||||
pub mod file;
|
||||
#[cfg(feature = "installer")]
|
||||
pub mod file_impl;
|
||||
pub mod general;
|
||||
pub mod package;
|
||||
pub mod user;
|
||||
|
||||
@@ -0,0 +1,785 @@
|
||||
#[cfg(target_os = "redox")]
|
||||
use anyhow::{anyhow, Context};
|
||||
use anyhow::{bail, Result};
|
||||
use pkg::Library;
|
||||
use rand::{rngs::OsRng, TryRngCore};
|
||||
use redoxfs::{unmount_path, Disk, DiskIo, FileSystem};
|
||||
use termion::input::TermRead;
|
||||
|
||||
use crate::config::file::FileConfig;
|
||||
use crate::config::package::PackageConfig;
|
||||
use crate::config::Config;
|
||||
use crate::disk_wrapper::DiskWrapper;
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::BTreeMap,
|
||||
env, fs,
|
||||
io::{self, Seek, SeekFrom, Write},
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
rc::Rc,
|
||||
sync::mpsc::channel,
|
||||
thread,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
pub struct DiskOption<'a> {
|
||||
pub bootloader_bios: &'a [u8],
|
||||
pub bootloader_efi: &'a [u8],
|
||||
pub password_opt: Option<&'a [u8]>,
|
||||
pub efi_partition_size: Option<u32>, //MiB
|
||||
pub skip_partitions: bool,
|
||||
}
|
||||
|
||||
fn get_target() -> String {
|
||||
env::var("TARGET").unwrap_or(
|
||||
option_env!("TARGET").map_or("x86_64-unknown-redox".to_string(), |x| x.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts a password to a serialized argon2rs hash, understandable
|
||||
/// by redox_users. If the password is blank, the hash is blank.
|
||||
fn hash_password(password: &str) -> Result<String> {
|
||||
if !password.is_empty() {
|
||||
let salt = format!("{:X}", OsRng.try_next_u64()?);
|
||||
let config = argon2::Config::default();
|
||||
let hash = argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &config)?;
|
||||
Ok(hash)
|
||||
} else {
|
||||
Ok("".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn syscall_error(err: syscall::Error) -> io::Error {
|
||||
io::Error::from_raw_os_error(err.errno)
|
||||
}
|
||||
|
||||
/// Returns a password collected from the user (plaintext)
|
||||
fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result<String> {
|
||||
let stdin = io::stdin();
|
||||
let mut stdin = stdin.lock();
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
print!("{}", prompt);
|
||||
let password = stdin.read_passwd(&mut stdout)?;
|
||||
|
||||
print!("\n{}", confirm_prompt);
|
||||
let confirm_password = stdin.read_passwd(&mut stdout)?;
|
||||
|
||||
// Note: Actually comparing two Option<String> values
|
||||
if confirm_password != password {
|
||||
bail!("passwords do not match");
|
||||
}
|
||||
Ok(password.unwrap_or("".to_string()))
|
||||
}
|
||||
|
||||
fn install_local_pkgar(cookbook: &str, target: &str, packagename: &str, dest: &Path) -> Result<()> {
|
||||
let head_path = get_head_path(packagename, dest);
|
||||
|
||||
let public_path = format!("{cookbook}/build/id_ed25519.pub.toml",);
|
||||
let pkgar_path = format!("{cookbook}/repo/{target}/{packagename}.pkgar");
|
||||
|
||||
let pkginfo_path = format!("{cookbook}/repo/{target}/{packagename}.toml");
|
||||
let pkginfo = pkg::Package::from_toml(&fs::read_to_string(pkginfo_path)?)?;
|
||||
|
||||
if pkginfo.version != "" {
|
||||
pkgar::extract(&public_path, &pkgar_path, dest)?;
|
||||
pkgar::split(&public_path, &pkgar_path, head_path, Option::<&str>::None)?;
|
||||
}
|
||||
|
||||
// Recursively install any runtime dependencies.
|
||||
for dep in pkginfo.depends.iter() {
|
||||
let depname = dep.as_str();
|
||||
if !get_head_path(depname, dest).exists() {
|
||||
println!("Installing runtime dependency for {packagename} from local repo: {depname}");
|
||||
install_local_pkgar(cookbook, target, depname, dest)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_head_path(packagename: &str, dest: &Path) -> PathBuf {
|
||||
dest.join(format!("pkg/packages/{packagename}.pkgar_head"))
|
||||
}
|
||||
|
||||
fn install_packages(config: &Config, dest: &Path, cookbook: Option<&str>) -> anyhow::Result<()> {
|
||||
let target = &get_target();
|
||||
|
||||
let callback = pkg::callback::IndicatifCallback::new();
|
||||
|
||||
let packages: Vec<&String> = config
|
||||
.packages
|
||||
.iter()
|
||||
.filter_map(|(packagename, package)| match package {
|
||||
PackageConfig::Build(rule) if rule == "ignore" => None,
|
||||
_ => Some(packagename),
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let Some(cookbook) = cookbook {
|
||||
let dest_pkg = dest.join("pkg/packages");
|
||||
if !Path::new(&dest_pkg).is_dir() {
|
||||
fs::create_dir_all(&dest_pkg)?;
|
||||
}
|
||||
for packagename in packages {
|
||||
if !get_head_path(packagename, dest).exists() {
|
||||
println!("Installing package from local repo: {}", packagename);
|
||||
install_local_pkgar(cookbook, target, packagename, dest)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut library = Library::new(dest, target, Rc::new(RefCell::new(callback)))?;
|
||||
for packagename in packages {
|
||||
if !get_head_path(packagename, dest).exists() {
|
||||
println!("Installing package from remote: {packagename}");
|
||||
library.install(vec![pkg::PackageName::new(packagename)?])?;
|
||||
}
|
||||
}
|
||||
library.apply()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_dir(
|
||||
config: Config,
|
||||
output_dir: impl AsRef<Path>,
|
||||
cookbook: Option<&str>,
|
||||
) -> Result<()> {
|
||||
//let mut context = liner::Context::new();
|
||||
|
||||
macro_rules! prompt {
|
||||
($dst:expr, $def:expr, $($arg:tt)*) => {
|
||||
if config.general.prompt.unwrap_or(true) {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"prompt not currently supported",
|
||||
))
|
||||
// match unwrap_or_prompt($dst, &mut context, &format!($($arg)*)) {
|
||||
// Ok(res) => if res.is_empty() {
|
||||
// Ok($def)
|
||||
// } else {
|
||||
// Ok(res)
|
||||
// },
|
||||
// Err(err) => Err(err)
|
||||
// }
|
||||
} else {
|
||||
Ok($dst.unwrap_or($def))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let output_dir = output_dir.as_ref();
|
||||
|
||||
let output_dir = output_dir.to_owned();
|
||||
|
||||
for file in &config.files {
|
||||
if !file.postinstall {
|
||||
file.create(&output_dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
install_packages(&config, &output_dir, cookbook)?;
|
||||
|
||||
for file in &config.files {
|
||||
if file.postinstall {
|
||||
file.create(&output_dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut passwd = String::new();
|
||||
let mut shadow = String::new();
|
||||
let mut next_uid = 1000;
|
||||
let mut next_gid = 1000;
|
||||
|
||||
let mut groups = vec![];
|
||||
|
||||
for (username, user) in config.users {
|
||||
// plaintext
|
||||
let password = if let Some(password) = user.password {
|
||||
password
|
||||
} else if config.general.prompt.unwrap_or(true) {
|
||||
prompt_password(
|
||||
&format!("{}: enter password: ", username),
|
||||
&format!("{}: confirm password: ", username),
|
||||
)?
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let uid = user.uid.unwrap_or(next_uid);
|
||||
|
||||
if uid >= next_uid {
|
||||
next_uid = uid + 1;
|
||||
}
|
||||
|
||||
let gid = user.gid.unwrap_or(next_gid);
|
||||
|
||||
if gid >= next_gid {
|
||||
next_gid = gid + 1;
|
||||
}
|
||||
|
||||
let name = prompt!(
|
||||
user.name,
|
||||
username.clone(),
|
||||
"{}: name (GECOS) [{}]: ",
|
||||
username,
|
||||
username
|
||||
)?;
|
||||
let home = prompt!(
|
||||
user.home,
|
||||
format!("/home/{}", username),
|
||||
"{}: home [/home/{}]: ",
|
||||
username,
|
||||
username
|
||||
)?;
|
||||
let shell = prompt!(
|
||||
user.shell,
|
||||
"/bin/ion".to_string(),
|
||||
"{}: shell [/bin/ion]: ",
|
||||
username
|
||||
)?;
|
||||
|
||||
println!("Adding user {username}:");
|
||||
println!("\tPassword: {password}");
|
||||
println!("\tUID: {uid}");
|
||||
println!("\tGID: {gid}");
|
||||
println!("\tName: {name}");
|
||||
println!("\tHome: {home}");
|
||||
println!("\tShell: {shell}");
|
||||
|
||||
FileConfig::new_directory(home.clone())
|
||||
.with_recursive_mod(0o777, uid, gid)
|
||||
.create(&output_dir)?;
|
||||
|
||||
if uid >= 1000 {
|
||||
// Create XDG user dirs
|
||||
//TODO: move to some autostart program?
|
||||
for xdg_folder in &[
|
||||
"Desktop",
|
||||
"Documents",
|
||||
"Downloads",
|
||||
"Music",
|
||||
"Pictures",
|
||||
"Public",
|
||||
"Templates",
|
||||
"Videos",
|
||||
".config",
|
||||
".local",
|
||||
".local/share",
|
||||
".local/share/Trash",
|
||||
".local/share/Trash/info",
|
||||
] {
|
||||
FileConfig::new_directory(format!("{}/{}", home, xdg_folder))
|
||||
.with_mod(0o0700, uid, gid)
|
||||
.create(&output_dir)?;
|
||||
}
|
||||
|
||||
FileConfig::new_file(
|
||||
format!("{}/.config/user-dirs.dirs", home),
|
||||
r#"# Produced by redox installer
|
||||
XDG_DESKTOP_DIR="$HOME/Desktop"
|
||||
XDG_DOCUMENTS_DIR="$HOME/Documents"
|
||||
XDG_DOWNLOAD_DIR="$HOME/Downloads"
|
||||
XDG_MUSIC_DIR="$HOME/Music"
|
||||
XDG_PICTURES_DIR="$HOME/Pictures"
|
||||
XDG_PUBLICSHARE_DIR="$HOME/Public"
|
||||
XDG_TEMPLATES_DIR="$HOME/Templates"
|
||||
XDG_VIDEOS_DIR="$HOME/Videos"
|
||||
"#
|
||||
.to_string(),
|
||||
)
|
||||
.with_mod(0o0600, uid, gid)
|
||||
.create(&output_dir)?;
|
||||
}
|
||||
|
||||
let password = hash_password(&password)?;
|
||||
|
||||
passwd.push_str(&format!("{username};{uid};{gid};{name};{home};{shell}\n",));
|
||||
shadow.push_str(&format!("{username};{password}\n"));
|
||||
groups.push((username.clone(), gid, vec![username]));
|
||||
}
|
||||
|
||||
for (group, group_config) in config.groups {
|
||||
// FIXME this assumes there is no overlap between auto-created groups for users
|
||||
// and explicitly specified groups.
|
||||
let gid = group_config.gid.unwrap_or(next_gid);
|
||||
|
||||
if gid >= next_gid {
|
||||
next_gid = gid + 1;
|
||||
}
|
||||
|
||||
groups.push((group, gid, group_config.members));
|
||||
}
|
||||
|
||||
if !passwd.is_empty() {
|
||||
FileConfig::new_file("/etc/passwd".to_string(), passwd).create(&output_dir)?;
|
||||
}
|
||||
|
||||
if !shadow.is_empty() {
|
||||
FileConfig::new_file("/etc/shadow".to_string(), shadow)
|
||||
.with_mod(0o0600, 0, 0)
|
||||
.create(&output_dir)?;
|
||||
}
|
||||
|
||||
if !groups.is_empty() {
|
||||
let mut groups_data = String::new();
|
||||
|
||||
for (name, gid, members) in groups {
|
||||
use std::fmt::Write;
|
||||
writeln!(groups_data, "{name};x;{gid};{}", members.join(","))?;
|
||||
|
||||
println!("Adding group {name}:");
|
||||
println!("\tGID: {gid}");
|
||||
println!("\tMembers: {}", members.join(", "));
|
||||
}
|
||||
|
||||
FileConfig::new_file("/etc/group".to_string(), groups_data)
|
||||
.with_mod(0o0600, 0, 0)
|
||||
.create(&output_dir)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn with_redoxfs<D, T, F>(disk: D, password_opt: Option<&[u8]>, callback: F) -> Result<T>
|
||||
where
|
||||
D: Disk + Send + 'static,
|
||||
F: FnOnce(FileSystem<D>) -> Result<T>,
|
||||
{
|
||||
let ctime = SystemTime::now().duration_since(UNIX_EPOCH)?;
|
||||
let fs = FileSystem::create(disk, password_opt, ctime.as_secs(), ctime.subsec_nanos())
|
||||
.map_err(syscall_error)?;
|
||||
callback(fs)
|
||||
}
|
||||
|
||||
pub fn with_redoxfs_mount<D, T, F>(fs: FileSystem<D>, callback: F) -> Result<T>
|
||||
where
|
||||
D: Disk + Send + 'static,
|
||||
F: FnOnce(&Path) -> Result<T>,
|
||||
{
|
||||
let mount_path = if cfg!(target_os = "redox") {
|
||||
format!("file.redox_installer_{}", process::id())
|
||||
} else {
|
||||
format!("/tmp/redox_installer_{}", process::id())
|
||||
};
|
||||
|
||||
if cfg!(not(target_os = "redox")) && !Path::new(&mount_path).exists() {
|
||||
fs::create_dir(&mount_path)?;
|
||||
}
|
||||
|
||||
let (tx, rx) = channel();
|
||||
let join_handle = {
|
||||
let mount_path = mount_path.clone();
|
||||
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())
|
||||
}
|
||||
};
|
||||
|
||||
unmount_path(&mount_path)?;
|
||||
|
||||
join_handle.join().unwrap();
|
||||
|
||||
if cfg!(not(target_os = "redox")) {
|
||||
fs::remove_dir_all(&mount_path)?;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn fetch_bootloaders(
|
||||
config: &Config,
|
||||
cookbook: Option<&str>,
|
||||
live: bool,
|
||||
) -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
let bootloader_dir =
|
||||
PathBuf::from(format!("/tmp/redox_installer_bootloader_{}", process::id()));
|
||||
|
||||
if bootloader_dir.exists() {
|
||||
fs::remove_dir_all(&bootloader_dir)?;
|
||||
}
|
||||
|
||||
fs::create_dir(&bootloader_dir)?;
|
||||
|
||||
let mut bootloader_config = Config::default();
|
||||
bootloader_config.general = config.general.clone();
|
||||
// Ensure a pkgar remote is available
|
||||
FileConfig {
|
||||
path: "/etc/pkg.d/50_redox".to_string(),
|
||||
data: "https://static.redox-os.org/pkg".to_string(),
|
||||
..Default::default()
|
||||
}
|
||||
.create(&bootloader_dir)?;
|
||||
bootloader_config
|
||||
.packages
|
||||
.insert("bootloader".to_string(), PackageConfig::default());
|
||||
install_packages(&bootloader_config, &bootloader_dir, cookbook)?;
|
||||
|
||||
let boot_dir = bootloader_dir.join("boot");
|
||||
let bios_path = boot_dir.join(if live {
|
||||
"bootloader-live.bios"
|
||||
} else {
|
||||
"bootloader.bios"
|
||||
});
|
||||
let efi_path = boot_dir.join(if live {
|
||||
"bootloader-live.efi"
|
||||
} else {
|
||||
"bootloader.efi"
|
||||
});
|
||||
|
||||
let bios_data = if bios_path.exists() {
|
||||
fs::read(bios_path)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let efi_data = if efi_path.exists() {
|
||||
fs::read(efi_path)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
fs::remove_dir_all(&bootloader_dir)?;
|
||||
|
||||
Ok((bios_data, efi_data))
|
||||
}
|
||||
|
||||
//TODO: make bootloaders use Option, dynamically create BIOS and EFI partitions
|
||||
pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback: F) -> Result<T>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
F: FnOnce(FileSystem<DiskIo<fscommon::StreamSlice<DiskWrapper>>>) -> Result<T>,
|
||||
{
|
||||
let target = get_target();
|
||||
|
||||
let bootloader_efi_name = match target.as_str() {
|
||||
"aarch64-unknown-redox" => "BOOTAA64.EFI",
|
||||
"i586-unknown-redox" | "i686-unknown-redox" => "BOOTIA32.EFI",
|
||||
"x86_64-unknown-redox" => "BOOTX64.EFI",
|
||||
"riscv64gc-unknown-redox" => "BOOTRISCV64.EFI",
|
||||
_ => {
|
||||
bail!("target '{target}' not supported");
|
||||
}
|
||||
};
|
||||
// Open disk and read metadata
|
||||
eprintln!("Opening disk {}", disk_path.as_ref().display());
|
||||
let mut disk_file = DiskWrapper::open(disk_path.as_ref())?;
|
||||
let disk_size = disk_file.size();
|
||||
let block_size = disk_file.block_size() as u64;
|
||||
|
||||
if disk_option.skip_partitions {
|
||||
return with_redoxfs(
|
||||
DiskIo(fscommon::StreamSlice::new(
|
||||
disk_file,
|
||||
0,
|
||||
disk_size.next_multiple_of(block_size),
|
||||
)?),
|
||||
disk_option.password_opt,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
let gpt_block_size = match block_size {
|
||||
512 => gpt::disk::LogicalBlockSize::Lb512,
|
||||
_ => {
|
||||
// TODO: support (and test) other block sizes
|
||||
bail!("block size {block_size} not supported");
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate partition offsets
|
||||
let gpt_reserved = 34 * 512; // GPT always reserves 34 512-byte sectors
|
||||
let mibi = 1024 * 1024;
|
||||
|
||||
// First megabyte of the disk is reserved for BIOS partition, wich includes GPT tables
|
||||
let bios_start = gpt_reserved / block_size;
|
||||
let bios_end = (mibi / block_size) - 1;
|
||||
|
||||
// Second megabyte of the disk is reserved for EFI partition
|
||||
let efi_start = bios_end + 1;
|
||||
let efi_size = if let Some(size) = disk_option.efi_partition_size {
|
||||
size as u64
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let efi_end = efi_start + (efi_size * mibi / block_size) - 1;
|
||||
|
||||
// The rest of the disk is RedoxFS, reserving the GPT table mirror at the end of disk
|
||||
let redoxfs_start = efi_end + 1;
|
||||
let redoxfs_end = ((((disk_size - gpt_reserved) / mibi) * mibi) / block_size) - 1;
|
||||
|
||||
// Format and install BIOS partition
|
||||
{
|
||||
// Write BIOS bootloader to disk
|
||||
eprintln!(
|
||||
"Write bootloader with size {:#x}",
|
||||
disk_option.bootloader_bios.len()
|
||||
);
|
||||
disk_file.seek(SeekFrom::Start(0))?;
|
||||
disk_file.write_all(&disk_option.bootloader_bios)?;
|
||||
|
||||
// Replace MBR tables with protective MBR
|
||||
// TODO: div_ceil
|
||||
let mbr_blocks = ((disk_size + block_size - 1) / block_size) - 1;
|
||||
eprintln!("Writing protective MBR with disk blocks {mbr_blocks:#x}");
|
||||
gpt::mbr::ProtectiveMBR::with_lb_size(mbr_blocks as u32)
|
||||
.update_conservative(&mut disk_file)?;
|
||||
|
||||
// Open disk, mark it as not initialized
|
||||
let mut gpt_disk = gpt::GptConfig::new()
|
||||
.initialized(false)
|
||||
.writable(true)
|
||||
.logical_block_size(gpt_block_size)
|
||||
.create_from_device(Box::new(&mut disk_file), None)?;
|
||||
|
||||
// Add BIOS boot partition
|
||||
let mut partitions = BTreeMap::new();
|
||||
let mut partition_id = 1;
|
||||
partitions.insert(
|
||||
partition_id,
|
||||
gpt::partition::Partition {
|
||||
part_type_guid: gpt::partition_types::BIOS,
|
||||
part_guid: uuid::Uuid::new_v4(),
|
||||
first_lba: bios_start,
|
||||
last_lba: bios_end,
|
||||
flags: 0, // TODO
|
||||
name: "BIOS".to_string(),
|
||||
},
|
||||
);
|
||||
partition_id += 1;
|
||||
|
||||
// Add EFI boot partition
|
||||
partitions.insert(
|
||||
partition_id,
|
||||
gpt::partition::Partition {
|
||||
part_type_guid: gpt::partition_types::EFI,
|
||||
part_guid: uuid::Uuid::new_v4(),
|
||||
first_lba: efi_start,
|
||||
last_lba: efi_end,
|
||||
flags: 0, // TODO
|
||||
name: "EFI".to_string(),
|
||||
},
|
||||
);
|
||||
partition_id += 1;
|
||||
|
||||
// Add RedoxFS partition
|
||||
partitions.insert(
|
||||
partition_id,
|
||||
gpt::partition::Partition {
|
||||
//TODO: Use REDOX_REDOXFS type (needs GPT crate changes)
|
||||
part_type_guid: gpt::partition_types::LINUX_FS,
|
||||
part_guid: uuid::Uuid::new_v4(),
|
||||
first_lba: redoxfs_start,
|
||||
last_lba: redoxfs_end,
|
||||
flags: 0,
|
||||
name: "REDOX".to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
eprintln!("Writing GPT tables: {partitions:#?}");
|
||||
|
||||
// Initialize GPT table
|
||||
gpt_disk.update_partitions(partitions)?;
|
||||
|
||||
// Write partition layout, returning disk file
|
||||
gpt_disk.write()?;
|
||||
}
|
||||
|
||||
// Format and install EFI partition
|
||||
{
|
||||
let disk_efi_start = efi_start * block_size;
|
||||
let disk_efi_end = (efi_end + 1) * block_size;
|
||||
let mut disk_efi =
|
||||
fscommon::StreamSlice::new(&mut disk_file, disk_efi_start, disk_efi_end)?;
|
||||
|
||||
eprintln!(
|
||||
"Formatting EFI partition with size {:#x}",
|
||||
disk_efi_end - disk_efi_start
|
||||
);
|
||||
fatfs::format_volume(&mut disk_efi, fatfs::FormatVolumeOptions::new())?;
|
||||
|
||||
eprintln!("Opening EFI partition");
|
||||
let fs = fatfs::FileSystem::new(&mut disk_efi, fatfs::FsOptions::new())?;
|
||||
|
||||
eprintln!("Creating EFI directory");
|
||||
let root_dir = fs.root_dir();
|
||||
root_dir.create_dir("EFI")?;
|
||||
|
||||
eprintln!("Creating EFI/BOOT directory");
|
||||
let efi_dir = root_dir.open_dir("EFI")?;
|
||||
efi_dir.create_dir("BOOT")?;
|
||||
|
||||
eprintln!(
|
||||
"Writing EFI/BOOT/{} file with size {:#x}",
|
||||
bootloader_efi_name,
|
||||
disk_option.bootloader_efi.len()
|
||||
);
|
||||
let boot_dir = efi_dir.open_dir("BOOT")?;
|
||||
let mut file = boot_dir.create_file(bootloader_efi_name)?;
|
||||
file.truncate()?;
|
||||
file.write_all(&disk_option.bootloader_efi)?;
|
||||
}
|
||||
|
||||
// Format and install RedoxFS partition
|
||||
eprintln!(
|
||||
"Installing to RedoxFS partition with size {:#x}",
|
||||
(redoxfs_end - redoxfs_start) * block_size
|
||||
);
|
||||
let disk_redoxfs = DiskIo(fscommon::StreamSlice::new(
|
||||
disk_file,
|
||||
redoxfs_start * block_size,
|
||||
(redoxfs_end + 1) * block_size,
|
||||
)?);
|
||||
with_redoxfs(disk_redoxfs, disk_option.password_opt, callback)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
pub fn try_fast_install<D: redoxfs::Disk, F: FnMut(u64, u64)>(
|
||||
_fs: &mut redoxfs::FileSystem<D>,
|
||||
_progress: F,
|
||||
) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Try fast install using live disk memory
|
||||
#[cfg(target_os = "redox")]
|
||||
pub fn try_fast_install<D: redoxfs::Disk, F: FnMut(u64, u64)>(
|
||||
fs: &mut redoxfs::FileSystem<D>,
|
||||
mut progress: F,
|
||||
) -> Result<bool> {
|
||||
use libredox::{call::MmapArgs, flag};
|
||||
use std::os::fd::AsRawFd;
|
||||
use syscall::PAGE_SIZE;
|
||||
|
||||
let phys = env::var("DISK_LIVE_ADDR")
|
||||
.ok()
|
||||
.and_then(|x| usize::from_str_radix(&x, 16).ok())
|
||||
.unwrap_or(0);
|
||||
let size = env::var("DISK_LIVE_SIZE")
|
||||
.ok()
|
||||
.and_then(|x| usize::from_str_radix(&x, 16).ok())
|
||||
.unwrap_or(0);
|
||||
if phys == 0 || size == 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let start = (phys / PAGE_SIZE) * PAGE_SIZE;
|
||||
let end = phys
|
||||
.checked_add(size)
|
||||
.context("phys + size overflow")?
|
||||
.next_multiple_of(PAGE_SIZE);
|
||||
let size = end - start;
|
||||
|
||||
let original = unsafe {
|
||||
//TODO: unmap this memory
|
||||
let file = fs::File::open("/scheme/memory/physical")?;
|
||||
let base = libredox::call::mmap(MmapArgs {
|
||||
fd: file.as_raw_fd() as usize,
|
||||
addr: core::ptr::null_mut(),
|
||||
offset: start as u64,
|
||||
length: size,
|
||||
prot: flag::PROT_READ,
|
||||
flags: flag::MAP_SHARED,
|
||||
})
|
||||
.map_err(|err| anyhow!("failed to mmap livedisk: {}", err))?;
|
||||
|
||||
std::slice::from_raw_parts(base as *const u8, size)
|
||||
};
|
||||
|
||||
struct DiskLive {
|
||||
original: &'static [u8],
|
||||
}
|
||||
|
||||
impl redoxfs::Disk for DiskLive {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
|
||||
let offset = (block * redoxfs::BLOCK_SIZE) as usize;
|
||||
if offset + buffer.len() > self.original.len() {
|
||||
return Err(syscall::Error::new(syscall::EINVAL));
|
||||
}
|
||||
buffer.copy_from_slice(&self.original[offset..offset + buffer.len()]);
|
||||
Ok(buffer.len())
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, _block: u64, _buffer: &[u8]) -> syscall::Result<usize> {
|
||||
Err(syscall::Error::new(syscall::EINVAL))
|
||||
}
|
||||
|
||||
fn size(&mut self) -> syscall::Result<u64> {
|
||||
Ok(self.original.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
let mut fs_old = redoxfs::FileSystem::open(DiskLive { original }, None, None, false)?;
|
||||
let size_old = fs_old.header.size();
|
||||
let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE;
|
||||
let used_old = size_old - free_old;
|
||||
redoxfs::clone(&mut fs_old, fs, move |used| {
|
||||
progress(used, used_old);
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn install_inner(
|
||||
config: Config,
|
||||
output: &Path,
|
||||
cookbook: Option<&str>,
|
||||
live: bool,
|
||||
write_bootloader: Option<&str>,
|
||||
) -> Result<()> {
|
||||
println!("Install {config:#?} to {}", output.display());
|
||||
|
||||
if output.is_dir() {
|
||||
install_dir(config, output, cookbook)
|
||||
} else {
|
||||
let (bootloader_bios, bootloader_efi) = fetch_bootloaders(&config, cookbook, live)?;
|
||||
if let Some(write_bootloader) = write_bootloader {
|
||||
std::fs::write(write_bootloader, &bootloader_efi)?;
|
||||
}
|
||||
let disk_option = DiskOption {
|
||||
bootloader_bios: &bootloader_bios,
|
||||
bootloader_efi: &bootloader_efi,
|
||||
password_opt: None,
|
||||
efi_partition_size: config.general.efi_partition_size,
|
||||
skip_partitions: config.general.skip_partitions.unwrap_or(false),
|
||||
};
|
||||
with_whole_disk(output, &disk_option, move |fs| {
|
||||
with_redoxfs_mount(fs, move |mount_path| {
|
||||
install_dir(config, mount_path, cookbook)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn install(
|
||||
config: Config,
|
||||
output: impl AsRef<Path>,
|
||||
cookbook: Option<&str>,
|
||||
live: bool,
|
||||
write_bootloader: Option<&str>,
|
||||
) -> Result<()> {
|
||||
install_inner(config, output.as_ref(), cookbook, live, write_bootloader)
|
||||
}
|
||||
+5
-782
@@ -2,790 +2,13 @@
|
||||
extern crate serde_derive;
|
||||
|
||||
mod config;
|
||||
#[cfg(feature = "installer")]
|
||||
mod disk_wrapper;
|
||||
#[cfg(feature = "installer")]
|
||||
mod installer;
|
||||
#[cfg(feature = "installer")]
|
||||
pub use crate::installer::*;
|
||||
|
||||
pub use crate::config::file::FileConfig;
|
||||
pub use crate::config::package::PackageConfig;
|
||||
pub use crate::config::Config;
|
||||
use crate::disk_wrapper::DiskWrapper;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
use anyhow::{anyhow, Context};
|
||||
use anyhow::{bail, Result};
|
||||
use pkg::Library;
|
||||
use rand::{rngs::OsRng, TryRngCore};
|
||||
use redoxfs::{unmount_path, Disk, DiskIo, FileSystem};
|
||||
use termion::input::TermRead;
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::BTreeMap,
|
||||
env, fs,
|
||||
io::{self, Seek, SeekFrom, Write},
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
rc::Rc,
|
||||
sync::mpsc::channel,
|
||||
thread,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
pub struct DiskOption<'a> {
|
||||
pub bootloader_bios: &'a [u8],
|
||||
pub bootloader_efi: &'a [u8],
|
||||
pub password_opt: Option<&'a [u8]>,
|
||||
pub efi_partition_size: Option<u32>, //MiB
|
||||
pub skip_partitions: bool,
|
||||
}
|
||||
|
||||
fn get_target() -> String {
|
||||
env::var("TARGET").unwrap_or(
|
||||
option_env!("TARGET").map_or("x86_64-unknown-redox".to_string(), |x| x.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts a password to a serialized argon2rs hash, understandable
|
||||
/// by redox_users. If the password is blank, the hash is blank.
|
||||
fn hash_password(password: &str) -> Result<String> {
|
||||
if !password.is_empty() {
|
||||
let salt = format!("{:X}", OsRng.try_next_u64()?);
|
||||
let config = argon2::Config::default();
|
||||
let hash = argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &config)?;
|
||||
Ok(hash)
|
||||
} else {
|
||||
Ok("".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn syscall_error(err: syscall::Error) -> io::Error {
|
||||
io::Error::from_raw_os_error(err.errno)
|
||||
}
|
||||
|
||||
/// Returns a password collected from the user (plaintext)
|
||||
fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result<String> {
|
||||
let stdin = io::stdin();
|
||||
let mut stdin = stdin.lock();
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
print!("{}", prompt);
|
||||
let password = stdin.read_passwd(&mut stdout)?;
|
||||
|
||||
print!("\n{}", confirm_prompt);
|
||||
let confirm_password = stdin.read_passwd(&mut stdout)?;
|
||||
|
||||
// Note: Actually comparing two Option<String> values
|
||||
if confirm_password != password {
|
||||
bail!("passwords do not match");
|
||||
}
|
||||
Ok(password.unwrap_or("".to_string()))
|
||||
}
|
||||
|
||||
fn install_local_pkgar(cookbook: &str, target: &str, packagename: &str, dest: &Path) -> Result<()> {
|
||||
let head_path = get_head_path(packagename, dest);
|
||||
|
||||
let public_path = format!("{cookbook}/build/id_ed25519.pub.toml",);
|
||||
let pkgar_path = format!("{cookbook}/repo/{target}/{packagename}.pkgar");
|
||||
|
||||
let pkginfo_path = format!("{cookbook}/repo/{target}/{packagename}.toml");
|
||||
let pkginfo = pkg::Package::from_toml(&fs::read_to_string(pkginfo_path)?)?;
|
||||
|
||||
if pkginfo.version != "" {
|
||||
pkgar::extract(&public_path, &pkgar_path, dest)?;
|
||||
pkgar::split(&public_path, &pkgar_path, head_path, Option::<&str>::None)?;
|
||||
}
|
||||
|
||||
// Recursively install any runtime dependencies.
|
||||
for dep in pkginfo.depends.iter() {
|
||||
let depname = dep.as_str();
|
||||
if !get_head_path(depname, dest).exists() {
|
||||
println!("Installing runtime dependency for {packagename} from local repo: {depname}");
|
||||
install_local_pkgar(cookbook, target, depname, dest)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_head_path(packagename: &str, dest: &Path) -> PathBuf {
|
||||
dest.join(format!("pkg/packages/{packagename}.pkgar_head"))
|
||||
}
|
||||
|
||||
fn install_packages(config: &Config, dest: &Path, cookbook: Option<&str>) -> anyhow::Result<()> {
|
||||
let target = &get_target();
|
||||
|
||||
let callback = pkg::callback::IndicatifCallback::new();
|
||||
|
||||
let packages: Vec<&String> = config
|
||||
.packages
|
||||
.iter()
|
||||
.filter_map(|(packagename, package)| match package {
|
||||
PackageConfig::Build(rule) if rule == "ignore" => None,
|
||||
_ => Some(packagename),
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let Some(cookbook) = cookbook {
|
||||
let dest_pkg = dest.join("pkg/packages");
|
||||
if !Path::new(&dest_pkg).is_dir() {
|
||||
fs::create_dir_all(&dest_pkg)?;
|
||||
}
|
||||
for packagename in packages {
|
||||
if !get_head_path(packagename, dest).exists() {
|
||||
println!("Installing package from local repo: {}", packagename);
|
||||
install_local_pkgar(cookbook, target, packagename, dest)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut library = Library::new(dest, target, Rc::new(RefCell::new(callback)))?;
|
||||
for packagename in packages {
|
||||
if !get_head_path(packagename, dest).exists() {
|
||||
println!("Installing package from remote: {packagename}");
|
||||
library.install(vec![pkg::PackageName::new(packagename)?])?;
|
||||
}
|
||||
}
|
||||
library.apply()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_dir(
|
||||
config: Config,
|
||||
output_dir: impl AsRef<Path>,
|
||||
cookbook: Option<&str>,
|
||||
) -> Result<()> {
|
||||
//let mut context = liner::Context::new();
|
||||
|
||||
macro_rules! prompt {
|
||||
($dst:expr, $def:expr, $($arg:tt)*) => {
|
||||
if config.general.prompt.unwrap_or(true) {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"prompt not currently supported",
|
||||
))
|
||||
// match unwrap_or_prompt($dst, &mut context, &format!($($arg)*)) {
|
||||
// Ok(res) => if res.is_empty() {
|
||||
// Ok($def)
|
||||
// } else {
|
||||
// Ok(res)
|
||||
// },
|
||||
// Err(err) => Err(err)
|
||||
// }
|
||||
} else {
|
||||
Ok($dst.unwrap_or($def))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let output_dir = output_dir.as_ref();
|
||||
|
||||
let output_dir = output_dir.to_owned();
|
||||
|
||||
for file in &config.files {
|
||||
if !file.postinstall {
|
||||
file.create(&output_dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
install_packages(&config, &output_dir, cookbook)?;
|
||||
|
||||
for file in &config.files {
|
||||
if file.postinstall {
|
||||
file.create(&output_dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut passwd = String::new();
|
||||
let mut shadow = String::new();
|
||||
let mut next_uid = 1000;
|
||||
let mut next_gid = 1000;
|
||||
|
||||
let mut groups = vec![];
|
||||
|
||||
for (username, user) in config.users {
|
||||
// plaintext
|
||||
let password = if let Some(password) = user.password {
|
||||
password
|
||||
} else if config.general.prompt.unwrap_or(true) {
|
||||
prompt_password(
|
||||
&format!("{}: enter password: ", username),
|
||||
&format!("{}: confirm password: ", username),
|
||||
)?
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let uid = user.uid.unwrap_or(next_uid);
|
||||
|
||||
if uid >= next_uid {
|
||||
next_uid = uid + 1;
|
||||
}
|
||||
|
||||
let gid = user.gid.unwrap_or(next_gid);
|
||||
|
||||
if gid >= next_gid {
|
||||
next_gid = gid + 1;
|
||||
}
|
||||
|
||||
let name = prompt!(
|
||||
user.name,
|
||||
username.clone(),
|
||||
"{}: name (GECOS) [{}]: ",
|
||||
username,
|
||||
username
|
||||
)?;
|
||||
let home = prompt!(
|
||||
user.home,
|
||||
format!("/home/{}", username),
|
||||
"{}: home [/home/{}]: ",
|
||||
username,
|
||||
username
|
||||
)?;
|
||||
let shell = prompt!(
|
||||
user.shell,
|
||||
"/bin/ion".to_string(),
|
||||
"{}: shell [/bin/ion]: ",
|
||||
username
|
||||
)?;
|
||||
|
||||
println!("Adding user {username}:");
|
||||
println!("\tPassword: {password}");
|
||||
println!("\tUID: {uid}");
|
||||
println!("\tGID: {gid}");
|
||||
println!("\tName: {name}");
|
||||
println!("\tHome: {home}");
|
||||
println!("\tShell: {shell}");
|
||||
|
||||
FileConfig::new_directory(home.clone())
|
||||
.with_recursive_mod(0o777, uid, gid)
|
||||
.create(&output_dir)?;
|
||||
|
||||
if uid >= 1000 {
|
||||
// Create XDG user dirs
|
||||
//TODO: move to some autostart program?
|
||||
for xdg_folder in &[
|
||||
"Desktop",
|
||||
"Documents",
|
||||
"Downloads",
|
||||
"Music",
|
||||
"Pictures",
|
||||
"Public",
|
||||
"Templates",
|
||||
"Videos",
|
||||
".config",
|
||||
".local",
|
||||
".local/share",
|
||||
".local/share/Trash",
|
||||
".local/share/Trash/info",
|
||||
] {
|
||||
FileConfig::new_directory(format!("{}/{}", home, xdg_folder))
|
||||
.with_mod(0o0700, uid, gid)
|
||||
.create(&output_dir)?;
|
||||
}
|
||||
|
||||
FileConfig::new_file(
|
||||
format!("{}/.config/user-dirs.dirs", home),
|
||||
r#"# Produced by redox installer
|
||||
XDG_DESKTOP_DIR="$HOME/Desktop"
|
||||
XDG_DOCUMENTS_DIR="$HOME/Documents"
|
||||
XDG_DOWNLOAD_DIR="$HOME/Downloads"
|
||||
XDG_MUSIC_DIR="$HOME/Music"
|
||||
XDG_PICTURES_DIR="$HOME/Pictures"
|
||||
XDG_PUBLICSHARE_DIR="$HOME/Public"
|
||||
XDG_TEMPLATES_DIR="$HOME/Templates"
|
||||
XDG_VIDEOS_DIR="$HOME/Videos"
|
||||
"#
|
||||
.to_string(),
|
||||
)
|
||||
.with_mod(0o0600, uid, gid)
|
||||
.create(&output_dir)?;
|
||||
}
|
||||
|
||||
let password = hash_password(&password)?;
|
||||
|
||||
passwd.push_str(&format!("{username};{uid};{gid};{name};{home};{shell}\n",));
|
||||
shadow.push_str(&format!("{username};{password}\n"));
|
||||
groups.push((username.clone(), gid, vec![username]));
|
||||
}
|
||||
|
||||
for (group, group_config) in config.groups {
|
||||
// FIXME this assumes there is no overlap between auto-created groups for users
|
||||
// and explicitly specified groups.
|
||||
let gid = group_config.gid.unwrap_or(next_gid);
|
||||
|
||||
if gid >= next_gid {
|
||||
next_gid = gid + 1;
|
||||
}
|
||||
|
||||
groups.push((group, gid, group_config.members));
|
||||
}
|
||||
|
||||
if !passwd.is_empty() {
|
||||
FileConfig::new_file("/etc/passwd".to_string(), passwd).create(&output_dir)?;
|
||||
}
|
||||
|
||||
if !shadow.is_empty() {
|
||||
FileConfig::new_file("/etc/shadow".to_string(), shadow)
|
||||
.with_mod(0o0600, 0, 0)
|
||||
.create(&output_dir)?;
|
||||
}
|
||||
|
||||
if !groups.is_empty() {
|
||||
let mut groups_data = String::new();
|
||||
|
||||
for (name, gid, members) in groups {
|
||||
use std::fmt::Write;
|
||||
writeln!(groups_data, "{name};x;{gid};{}", members.join(","))?;
|
||||
|
||||
println!("Adding group {name}:");
|
||||
println!("\tGID: {gid}");
|
||||
println!("\tMembers: {}", members.join(", "));
|
||||
}
|
||||
|
||||
FileConfig::new_file("/etc/group".to_string(), groups_data)
|
||||
.with_mod(0o0600, 0, 0)
|
||||
.create(&output_dir)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn with_redoxfs<D, T, F>(disk: D, password_opt: Option<&[u8]>, callback: F) -> Result<T>
|
||||
where
|
||||
D: Disk + Send + 'static,
|
||||
F: FnOnce(FileSystem<D>) -> Result<T>,
|
||||
{
|
||||
let ctime = SystemTime::now().duration_since(UNIX_EPOCH)?;
|
||||
let fs = FileSystem::create(disk, password_opt, ctime.as_secs(), ctime.subsec_nanos())
|
||||
.map_err(syscall_error)?;
|
||||
callback(fs)
|
||||
}
|
||||
|
||||
pub fn with_redoxfs_mount<D, T, F>(fs: FileSystem<D>, callback: F) -> Result<T>
|
||||
where
|
||||
D: Disk + Send + 'static,
|
||||
F: FnOnce(&Path) -> Result<T>,
|
||||
{
|
||||
let mount_path = if cfg!(target_os = "redox") {
|
||||
format!("file.redox_installer_{}", process::id())
|
||||
} else {
|
||||
format!("/tmp/redox_installer_{}", process::id())
|
||||
};
|
||||
|
||||
if cfg!(not(target_os = "redox")) && !Path::new(&mount_path).exists() {
|
||||
fs::create_dir(&mount_path)?;
|
||||
}
|
||||
|
||||
let (tx, rx) = channel();
|
||||
let join_handle = {
|
||||
let mount_path = mount_path.clone();
|
||||
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())
|
||||
}
|
||||
};
|
||||
|
||||
unmount_path(&mount_path)?;
|
||||
|
||||
join_handle.join().unwrap();
|
||||
|
||||
if cfg!(not(target_os = "redox")) {
|
||||
fs::remove_dir_all(&mount_path)?;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn fetch_bootloaders(
|
||||
config: &Config,
|
||||
cookbook: Option<&str>,
|
||||
live: bool,
|
||||
) -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
let bootloader_dir =
|
||||
PathBuf::from(format!("/tmp/redox_installer_bootloader_{}", process::id()));
|
||||
|
||||
if bootloader_dir.exists() {
|
||||
fs::remove_dir_all(&bootloader_dir)?;
|
||||
}
|
||||
|
||||
fs::create_dir(&bootloader_dir)?;
|
||||
|
||||
let mut bootloader_config = Config::default();
|
||||
bootloader_config.general = config.general.clone();
|
||||
// Ensure a pkgar remote is available
|
||||
FileConfig {
|
||||
path: "/etc/pkg.d/50_redox".to_string(),
|
||||
data: "https://static.redox-os.org/pkg".to_string(),
|
||||
..Default::default()
|
||||
}
|
||||
.create(&bootloader_dir)?;
|
||||
bootloader_config
|
||||
.packages
|
||||
.insert("bootloader".to_string(), PackageConfig::default());
|
||||
install_packages(&bootloader_config, &bootloader_dir, cookbook)?;
|
||||
|
||||
let boot_dir = bootloader_dir.join("boot");
|
||||
let bios_path = boot_dir.join(if live {
|
||||
"bootloader-live.bios"
|
||||
} else {
|
||||
"bootloader.bios"
|
||||
});
|
||||
let efi_path = boot_dir.join(if live {
|
||||
"bootloader-live.efi"
|
||||
} else {
|
||||
"bootloader.efi"
|
||||
});
|
||||
|
||||
let bios_data = if bios_path.exists() {
|
||||
fs::read(bios_path)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let efi_data = if efi_path.exists() {
|
||||
fs::read(efi_path)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
fs::remove_dir_all(&bootloader_dir)?;
|
||||
|
||||
Ok((bios_data, efi_data))
|
||||
}
|
||||
|
||||
//TODO: make bootloaders use Option, dynamically create BIOS and EFI partitions
|
||||
pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback: F) -> Result<T>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
F: FnOnce(FileSystem<DiskIo<fscommon::StreamSlice<DiskWrapper>>>) -> Result<T>,
|
||||
{
|
||||
let target = get_target();
|
||||
|
||||
let bootloader_efi_name = match target.as_str() {
|
||||
"aarch64-unknown-redox" => "BOOTAA64.EFI",
|
||||
"i586-unknown-redox" | "i686-unknown-redox" => "BOOTIA32.EFI",
|
||||
"x86_64-unknown-redox" => "BOOTX64.EFI",
|
||||
"riscv64gc-unknown-redox" => "BOOTRISCV64.EFI",
|
||||
_ => {
|
||||
bail!("target '{target}' not supported");
|
||||
}
|
||||
};
|
||||
// Open disk and read metadata
|
||||
eprintln!("Opening disk {}", disk_path.as_ref().display());
|
||||
let mut disk_file = DiskWrapper::open(disk_path.as_ref())?;
|
||||
let disk_size = disk_file.size();
|
||||
let block_size = disk_file.block_size() as u64;
|
||||
|
||||
if disk_option.skip_partitions {
|
||||
return with_redoxfs(
|
||||
DiskIo(fscommon::StreamSlice::new(
|
||||
disk_file,
|
||||
0,
|
||||
disk_size.next_multiple_of(block_size),
|
||||
)?),
|
||||
disk_option.password_opt,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
let gpt_block_size = match block_size {
|
||||
512 => gpt::disk::LogicalBlockSize::Lb512,
|
||||
_ => {
|
||||
// TODO: support (and test) other block sizes
|
||||
bail!("block size {block_size} not supported");
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate partition offsets
|
||||
let gpt_reserved = 34 * 512; // GPT always reserves 34 512-byte sectors
|
||||
let mibi = 1024 * 1024;
|
||||
|
||||
// First megabyte of the disk is reserved for BIOS partition, wich includes GPT tables
|
||||
let bios_start = gpt_reserved / block_size;
|
||||
let bios_end = (mibi / block_size) - 1;
|
||||
|
||||
// Second megabyte of the disk is reserved for EFI partition
|
||||
let efi_start = bios_end + 1;
|
||||
let efi_size = if let Some(size) = disk_option.efi_partition_size {
|
||||
size as u64
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let efi_end = efi_start + (efi_size * mibi / block_size) - 1;
|
||||
|
||||
// The rest of the disk is RedoxFS, reserving the GPT table mirror at the end of disk
|
||||
let redoxfs_start = efi_end + 1;
|
||||
let redoxfs_end = ((((disk_size - gpt_reserved) / mibi) * mibi) / block_size) - 1;
|
||||
|
||||
// Format and install BIOS partition
|
||||
{
|
||||
// Write BIOS bootloader to disk
|
||||
eprintln!(
|
||||
"Write bootloader with size {:#x}",
|
||||
disk_option.bootloader_bios.len()
|
||||
);
|
||||
disk_file.seek(SeekFrom::Start(0))?;
|
||||
disk_file.write_all(&disk_option.bootloader_bios)?;
|
||||
|
||||
// Replace MBR tables with protective MBR
|
||||
// TODO: div_ceil
|
||||
let mbr_blocks = ((disk_size + block_size - 1) / block_size) - 1;
|
||||
eprintln!("Writing protective MBR with disk blocks {mbr_blocks:#x}");
|
||||
gpt::mbr::ProtectiveMBR::with_lb_size(mbr_blocks as u32)
|
||||
.update_conservative(&mut disk_file)?;
|
||||
|
||||
// Open disk, mark it as not initialized
|
||||
let mut gpt_disk = gpt::GptConfig::new()
|
||||
.initialized(false)
|
||||
.writable(true)
|
||||
.logical_block_size(gpt_block_size)
|
||||
.create_from_device(Box::new(&mut disk_file), None)?;
|
||||
|
||||
// Add BIOS boot partition
|
||||
let mut partitions = BTreeMap::new();
|
||||
let mut partition_id = 1;
|
||||
partitions.insert(
|
||||
partition_id,
|
||||
gpt::partition::Partition {
|
||||
part_type_guid: gpt::partition_types::BIOS,
|
||||
part_guid: uuid::Uuid::new_v4(),
|
||||
first_lba: bios_start,
|
||||
last_lba: bios_end,
|
||||
flags: 0, // TODO
|
||||
name: "BIOS".to_string(),
|
||||
},
|
||||
);
|
||||
partition_id += 1;
|
||||
|
||||
// Add EFI boot partition
|
||||
partitions.insert(
|
||||
partition_id,
|
||||
gpt::partition::Partition {
|
||||
part_type_guid: gpt::partition_types::EFI,
|
||||
part_guid: uuid::Uuid::new_v4(),
|
||||
first_lba: efi_start,
|
||||
last_lba: efi_end,
|
||||
flags: 0, // TODO
|
||||
name: "EFI".to_string(),
|
||||
},
|
||||
);
|
||||
partition_id += 1;
|
||||
|
||||
// Add RedoxFS partition
|
||||
partitions.insert(
|
||||
partition_id,
|
||||
gpt::partition::Partition {
|
||||
//TODO: Use REDOX_REDOXFS type (needs GPT crate changes)
|
||||
part_type_guid: gpt::partition_types::LINUX_FS,
|
||||
part_guid: uuid::Uuid::new_v4(),
|
||||
first_lba: redoxfs_start,
|
||||
last_lba: redoxfs_end,
|
||||
flags: 0,
|
||||
name: "REDOX".to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
eprintln!("Writing GPT tables: {partitions:#?}");
|
||||
|
||||
// Initialize GPT table
|
||||
gpt_disk.update_partitions(partitions)?;
|
||||
|
||||
// Write partition layout, returning disk file
|
||||
gpt_disk.write()?;
|
||||
}
|
||||
|
||||
// Format and install EFI partition
|
||||
{
|
||||
let disk_efi_start = efi_start * block_size;
|
||||
let disk_efi_end = (efi_end + 1) * block_size;
|
||||
let mut disk_efi =
|
||||
fscommon::StreamSlice::new(&mut disk_file, disk_efi_start, disk_efi_end)?;
|
||||
|
||||
eprintln!(
|
||||
"Formatting EFI partition with size {:#x}",
|
||||
disk_efi_end - disk_efi_start
|
||||
);
|
||||
fatfs::format_volume(&mut disk_efi, fatfs::FormatVolumeOptions::new())?;
|
||||
|
||||
eprintln!("Opening EFI partition");
|
||||
let fs = fatfs::FileSystem::new(&mut disk_efi, fatfs::FsOptions::new())?;
|
||||
|
||||
eprintln!("Creating EFI directory");
|
||||
let root_dir = fs.root_dir();
|
||||
root_dir.create_dir("EFI")?;
|
||||
|
||||
eprintln!("Creating EFI/BOOT directory");
|
||||
let efi_dir = root_dir.open_dir("EFI")?;
|
||||
efi_dir.create_dir("BOOT")?;
|
||||
|
||||
eprintln!(
|
||||
"Writing EFI/BOOT/{} file with size {:#x}",
|
||||
bootloader_efi_name,
|
||||
disk_option.bootloader_efi.len()
|
||||
);
|
||||
let boot_dir = efi_dir.open_dir("BOOT")?;
|
||||
let mut file = boot_dir.create_file(bootloader_efi_name)?;
|
||||
file.truncate()?;
|
||||
file.write_all(&disk_option.bootloader_efi)?;
|
||||
}
|
||||
|
||||
// Format and install RedoxFS partition
|
||||
eprintln!(
|
||||
"Installing to RedoxFS partition with size {:#x}",
|
||||
(redoxfs_end - redoxfs_start) * block_size
|
||||
);
|
||||
let disk_redoxfs = DiskIo(fscommon::StreamSlice::new(
|
||||
disk_file,
|
||||
redoxfs_start * block_size,
|
||||
(redoxfs_end + 1) * block_size,
|
||||
)?);
|
||||
with_redoxfs(disk_redoxfs, disk_option.password_opt, callback)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
pub fn try_fast_install<D: redoxfs::Disk, F: FnMut(u64, u64)>(
|
||||
_fs: &mut redoxfs::FileSystem<D>,
|
||||
_progress: F,
|
||||
) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Try fast install using live disk memory
|
||||
#[cfg(target_os = "redox")]
|
||||
pub fn try_fast_install<D: redoxfs::Disk, F: FnMut(u64, u64)>(
|
||||
fs: &mut redoxfs::FileSystem<D>,
|
||||
mut progress: F,
|
||||
) -> Result<bool> {
|
||||
use libredox::{call::MmapArgs, flag};
|
||||
use std::os::fd::AsRawFd;
|
||||
use syscall::PAGE_SIZE;
|
||||
|
||||
let phys = env::var("DISK_LIVE_ADDR")
|
||||
.ok()
|
||||
.and_then(|x| usize::from_str_radix(&x, 16).ok())
|
||||
.unwrap_or(0);
|
||||
let size = env::var("DISK_LIVE_SIZE")
|
||||
.ok()
|
||||
.and_then(|x| usize::from_str_radix(&x, 16).ok())
|
||||
.unwrap_or(0);
|
||||
if phys == 0 || size == 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let start = (phys / PAGE_SIZE) * PAGE_SIZE;
|
||||
let end = phys
|
||||
.checked_add(size)
|
||||
.context("phys + size overflow")?
|
||||
.next_multiple_of(PAGE_SIZE);
|
||||
let size = end - start;
|
||||
|
||||
let original = unsafe {
|
||||
//TODO: unmap this memory
|
||||
let file = fs::File::open("/scheme/memory/physical")?;
|
||||
let base = libredox::call::mmap(MmapArgs {
|
||||
fd: file.as_raw_fd() as usize,
|
||||
addr: core::ptr::null_mut(),
|
||||
offset: start as u64,
|
||||
length: size,
|
||||
prot: flag::PROT_READ,
|
||||
flags: flag::MAP_SHARED,
|
||||
})
|
||||
.map_err(|err| anyhow!("failed to mmap livedisk: {}", err))?;
|
||||
|
||||
std::slice::from_raw_parts(base as *const u8, size)
|
||||
};
|
||||
|
||||
struct DiskLive {
|
||||
original: &'static [u8],
|
||||
}
|
||||
|
||||
impl redoxfs::Disk for DiskLive {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
|
||||
let offset = (block * redoxfs::BLOCK_SIZE) as usize;
|
||||
if offset + buffer.len() > self.original.len() {
|
||||
return Err(syscall::Error::new(syscall::EINVAL));
|
||||
}
|
||||
buffer.copy_from_slice(&self.original[offset..offset + buffer.len()]);
|
||||
Ok(buffer.len())
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, _block: u64, _buffer: &[u8]) -> syscall::Result<usize> {
|
||||
Err(syscall::Error::new(syscall::EINVAL))
|
||||
}
|
||||
|
||||
fn size(&mut self) -> syscall::Result<u64> {
|
||||
Ok(self.original.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
let mut fs_old = redoxfs::FileSystem::open(DiskLive { original }, None, None, false)?;
|
||||
let size_old = fs_old.header.size();
|
||||
let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE;
|
||||
let used_old = size_old - free_old;
|
||||
redoxfs::clone(&mut fs_old, fs, move |used| {
|
||||
progress(used, used_old);
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn install_inner(
|
||||
config: Config,
|
||||
output: &Path,
|
||||
cookbook: Option<&str>,
|
||||
live: bool,
|
||||
write_bootloader: Option<&str>,
|
||||
) -> Result<()> {
|
||||
println!("Install {config:#?} to {}", output.display());
|
||||
|
||||
if output.is_dir() {
|
||||
install_dir(config, output, cookbook)
|
||||
} else {
|
||||
let (bootloader_bios, bootloader_efi) = fetch_bootloaders(&config, cookbook, live)?;
|
||||
if let Some(write_bootloader) = write_bootloader {
|
||||
std::fs::write(write_bootloader, &bootloader_efi)?;
|
||||
}
|
||||
let disk_option = DiskOption {
|
||||
bootloader_bios: &bootloader_bios,
|
||||
bootloader_efi: &bootloader_efi,
|
||||
password_opt: None,
|
||||
efi_partition_size: config.general.efi_partition_size,
|
||||
skip_partitions: config.general.skip_partitions.unwrap_or(false),
|
||||
};
|
||||
with_whole_disk(output, &disk_option, move |fs| {
|
||||
with_redoxfs_mount(fs, move |mount_path| {
|
||||
install_dir(config, mount_path, cookbook)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn install(
|
||||
config: Config,
|
||||
output: impl AsRef<Path>,
|
||||
cookbook: Option<&str>,
|
||||
live: bool,
|
||||
write_bootloader: Option<&str>,
|
||||
) -> Result<()> {
|
||||
install_inner(config, output.as_ref(), cookbook, live, write_bootloader)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user