Merge branch 'ext' into 'master'

Modernize code and add support for local binary packages

See merge request redox-os/installer!38
This commit is contained in:
Jeremy Soller
2024-10-06 15:32:39 +00:00
10 changed files with 413 additions and 369 deletions
Generated
+13 -58
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@@ -68,6 +68,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "arg_parser"
version = "0.1.0"
@@ -502,28 +508,6 @@ dependencies = [
"version_check 0.9.4",
]
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"synstructure",
]
[[package]]
name = "fatfs"
version = "0.3.6"
@@ -1165,9 +1149,9 @@ dependencies = [
name = "redox_installer"
version = "0.2.24"
dependencies = [
"anyhow",
"arg_parser",
"cc",
"failure",
"fatfs",
"fscommon",
"gpt",
@@ -1443,7 +1427,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn",
]
[[package]]
@@ -1511,17 +1495,6 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.68"
@@ -1533,18 +1506,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-xid",
]
[[package]]
name = "tar"
version = "0.4.41"
@@ -1627,7 +1588,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn",
]
[[package]]
@@ -1753,12 +1714,6 @@ version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "untrusted"
version = "0.6.2"
@@ -1901,7 +1856,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.68",
"syn",
"wasm-bindgen-shared",
]
@@ -1923,7 +1878,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -2171,5 +2126,5 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn",
]
+2 -1
View File
@@ -6,6 +6,7 @@ license = "MIT"
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
repository = "https://gitlab.redox-os.org/redox-os/installer"
default-run = "redox_installer"
edition = "2021"
[[bin]]
name = "redox_installer"
@@ -24,9 +25,9 @@ name = "redox_installer"
path = "src/lib.rs"
[dependencies]
anyhow = "1.0.89"
arg_parser = "0.1.0"
cc = "=1.0.99" # Hack for ring 0.13.5 not building
failure = "0.1.8"
fatfs = "0.3.0"
fscommon = "0.1.1"
gpt = "3.0.0"
+3 -1
View File
@@ -114,6 +114,7 @@ fn main() {
version: None,
git: None,
path: None,
pkg_path: None,
} => false,
_ => true,
})
@@ -143,7 +144,8 @@ fn main() {
};
if let Some(path) = parser.args.get(0) {
if let Err(err) = redox_installer::install(config, path, cookbook, parser.found("live"))
if let Err(err) =
redox_installer::install(config, path, cookbook.as_deref(), parser.found("live"))
{
writeln!(stderr, "installer: failed to install: {}", err).unwrap();
process::exit(1);
+81 -82
View File
@@ -1,24 +1,15 @@
extern crate arg_parser;
#[macro_use] extern crate failure;
extern crate pkgar;
extern crate pkgar_core;
extern crate pkgar_keys;
extern crate redox_installer;
extern crate serde;
extern crate termion;
extern crate toml;
use pkgar::{PackageHead, ext::EntryExt};
use anyhow::{anyhow, bail, Result};
use pkgar::{ext::EntryExt, PackageHead};
use pkgar_core::PackageSrc;
use pkgar_keys::PublicKeyFile;
use redox_installer::{Config, with_whole_disk, DiskOption};
use redox_installer::{with_whole_disk, Config, DiskOption};
use std::{
ffi::OsStr,
fs,
io::{self, Read, Write},
os::unix::fs::{MetadataExt, OpenOptionsExt, symlink},
os::unix::fs::{symlink, MetadataExt, OpenOptionsExt},
path::Path,
process
process,
};
use termion::input::TermRead;
@@ -29,52 +20,54 @@ fn disk_paths(_paths: &mut Vec<(String, u64)>) {}
fn disk_paths(paths: &mut Vec<(String, u64)>) {
let mut schemes = Vec::new();
match fs::read_dir("/scheme") {
Ok(entries) => for entry_res in entries {
if let Ok(entry) = entry_res {
let path = entry.path();
if let Ok(path_str) = path.into_os_string().into_string() {
let scheme = path_str.trim_start_matches(':').trim_matches('/');
if scheme.starts_with("disk") {
if scheme == "disk/live" {
// Skip live disks
continue;
}
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
let path = entry.path();
if let Ok(path_str) = path.into_os_string().into_string() {
let scheme = path_str.trim_start_matches(':').trim_matches('/');
if scheme.starts_with("disk") {
if scheme == "disk/live" {
// Skip live disks
continue;
}
schemes.push(format!("{}:", scheme));
schemes.push(format!("{}:", scheme));
}
}
}
}
},
}
Err(err) => {
eprintln!("installer_tui: failed to list schemes: {}", err);
}
}
for scheme in schemes {
let is_dir = fs::metadata(&scheme)
.map(|x| x.is_dir())
.unwrap_or(false);
let is_dir = fs::metadata(&scheme).map(|x| x.is_dir()).unwrap_or(false);
if is_dir {
match fs::read_dir(&scheme) {
Ok(entries) => for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(file_name) = entry.file_name().into_string() {
if file_name.contains('p') {
// Skip partitions
continue;
}
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(file_name) = entry.file_name().into_string() {
if file_name.contains('p') {
// Skip partitions
continue;
}
if let Ok(path) = entry.path().into_os_string().into_string() {
if let Ok(metadata) = entry.metadata() {
let size = metadata.len();
if size > 0 {
paths.push((path, size));
if let Ok(path) = entry.path().into_os_string().into_string() {
if let Ok(metadata) = entry.metadata() {
let size = metadata.len();
if size > 0 {
paths.push((path, size));
}
}
}
}
}
}
},
}
Err(err) => {
eprintln!("installer_tui: failed to list '{}': {}", scheme, err);
}
@@ -102,14 +95,14 @@ fn format_size(size: u64) -> String {
}
}
fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<(), failure::Error> {
fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<()> {
if let Some(parent) = dest.parent() {
// Parent may be a symlink
if ! parent.is_symlink() {
if !parent.is_symlink() {
match fs::create_dir_all(&parent) {
Ok(()) => (),
Err(err) => {
return Err(format_err!("failed to create directory {}: {}", parent.display(), err));
bail!("failed to create directory {}: {}", parent.display(), err);
}
}
}
@@ -118,36 +111,47 @@ fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<(), failure::Err
let metadata = match fs::symlink_metadata(&src) {
Ok(ok) => ok,
Err(err) => {
return Err(format_err!("failed to read metadata of {}: {}", src.display(), err));
},
bail!("failed to read metadata of {}: {}", src.display(), err);
}
};
if metadata.file_type().is_symlink() {
let real_src = match fs::read_link(&src) {
Ok(ok) => ok,
Err(err) => {
return Err(format_err!("failed to read link {}: {}", src.display(), err));
bail!("failed to read link {}: {}", src.display(), err);
}
};
match symlink(&real_src, &dest) {
Ok(()) => (),
Err(err) => {
return Err(format_err!("failed to copy link {} ({}) to {}: {}", src.display(), real_src.display(), dest.display(), err));
},
bail!(
"failed to copy link {} ({}) to {}: {}",
src.display(),
real_src.display(),
dest.display(),
err
);
}
}
} else {
let mut src_file = match fs::File::open(&src) {
Ok(ok) => ok,
Err(err) => {
return Err(format_err!("failed to open file {}: {}", src.display(), err));
bail!("failed to open file {}: {}", src.display(), err);
}
};
let mut dest_file = match fs::OpenOptions::new().write(true).create_new(true).mode(metadata.mode()).open(&dest) {
let mut dest_file = match fs::OpenOptions::new()
.write(true)
.create_new(true)
.mode(metadata.mode())
.open(&dest)
{
Ok(ok) => ok,
Err(err) => {
return Err(format_err!("failed to create file {}: {}", dest.display(), err));
bail!("failed to create file {}: {}", dest.display(), err);
}
};
@@ -155,7 +159,7 @@ fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<(), failure::Err
let count = match src_file.read(buf) {
Ok(ok) => ok,
Err(err) => {
return Err(format_err!("failed to read file {}: {}", src.display(), err));
bail!("failed to read file {}: {}", src.display(), err);
}
};
@@ -166,7 +170,7 @@ fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<(), failure::Err
match dest_file.write_all(&buf[..count]) {
Ok(()) => (),
Err(err) => {
return Err(format_err!("failed to write file {}: {}", dest.display(), err));
bail!("failed to write file {}: {}", dest.display(), err);
}
}
}
@@ -175,7 +179,11 @@ fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<(), failure::Err
Ok(())
}
fn package_files(root_path: &Path, config: &mut Config, files: &mut Vec<String>) -> Result<(), pkgar::Error> {
fn package_files(
root_path: &Path,
config: &mut Config,
files: &mut Vec<String>,
) -> Result<(), pkgar::Error> {
//TODO: Remove packages from config where all files are located (and have valid shasum?)
config.packages.clear();
@@ -189,18 +197,15 @@ fn package_files(root_path: &Path, config: &mut Config, files: &mut Vec<String>)
if pkg_path.extension() == Some(OsStr::new("pkgar_head")) {
let mut pkg = PackageHead::new(&pkg_path, &root_path, &pkey)?;
for entry in pkg.read_entries()? {
files.push(
entry
.check_path()?
.to_str().unwrap()
.to_string()
);
files.push(entry.check_path()?.to_str().unwrap().to_string());
}
files.push(
pkg_path
.strip_prefix(root_path).unwrap()
.to_str().unwrap()
.to_string()
.strip_prefix(root_path)
.unwrap()
.to_str()
.unwrap()
.to_string(),
);
}
}
@@ -227,7 +232,7 @@ fn choose_disk() -> String {
Ok(0) => {
eprintln!("installer_tui: failed to read line: end of input");
process::exit(1);
},
}
Ok(_) => (),
Err(err) => {
eprintln!("installer_tui: failed to read line: {}", err);
@@ -242,7 +247,7 @@ fn choose_disk() -> String {
} else {
eprintln!("{} not from 1 to {}", i, paths.len());
}
},
}
Err(err) => {
eprintln!("invalid input: {}", err);
}
@@ -262,7 +267,7 @@ fn choose_password() -> Option<String> {
eprintln!();
if password.is_empty() {
return None;
return None;
}
Some(password)
@@ -311,28 +316,22 @@ fn main() {
password_opt: password_opt.as_ref().map(|x| x.as_bytes()),
efi_partition_size: None,
};
let res = with_whole_disk(&disk_path, &disk_option, |mount_path| -> Result<(), failure::Error> {
let res = with_whole_disk(&disk_path, &disk_option, |mount_path| -> Result<()> {
let mut config: Config = Config::from_file(&root_path.join("filesystem.toml"))?;
// Copy filesystem.toml, which is not packaged
let mut files = vec![
"filesystem.toml".to_string(),
];
let mut files = vec!["filesystem.toml".to_string()];
// Copy files from locally installed packages
if let Err(err) = package_files(&root_path, &mut config, &mut files) {
return Err(format_err!("failed to read package files: {}", err));
}
package_files(&root_path, &mut config, &mut files)
// TODO: implement Error trait
.map_err(|err| anyhow!("failed to read package files: {err}"))?;
// Perform config install (after packages have been converted to files)
eprintln!("configuring system");
let cookbook: Option<&'static str> = None;
redox_installer::install_dir(config, mount_path, cookbook).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
err
)
})?;
redox_installer::install_dir(config, mount_path, cookbook)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
// Sort and remove duplicates
files.sort();
@@ -357,7 +356,7 @@ fn main() {
Ok(()) => {
eprintln!("installer_tui: installed successfully");
process::exit(0);
},
}
Err(err) => {
eprintln!("installer_tui: failed to install: {}", err);
process::exit(1);
+7 -11
View File
@@ -1,12 +1,11 @@
use Result;
use crate::Result;
use libc::{gid_t, uid_t};
use std::io::{Error, Write};
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::{PermissionsExt, symlink};
use std::os::unix::fs::{symlink, PermissionsExt};
use std::path::Path;
//type Result<T> = std::result::Result<T, Error>;
@@ -48,8 +47,7 @@ pub struct FileConfig {
impl 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);
let target_file = prefix.as_ref().join(path);
if self.directory {
println!("Create directory {}", target_file.display());
@@ -76,11 +74,9 @@ impl FileConfig {
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 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);
+1 -1
View File
@@ -3,7 +3,7 @@ pub struct GeneralConfig {
pub prompt: Option<bool>,
// Allow config to specify cookbook recipe or binary package as default
pub repo_binary: Option<bool>,
pub filesystem_size: Option<u32>, //MiB
pub filesystem_size: Option<u32>, //MiB
pub efi_partition_size: Option<u32>, //MiB
}
+7 -4
View File
@@ -3,6 +3,9 @@ use std::fs;
use std::mem;
use std::path::{Path, PathBuf};
use anyhow::bail;
use anyhow::Result;
pub mod file;
pub mod general;
pub mod package;
@@ -25,16 +28,16 @@ pub struct Config {
}
impl Config {
pub fn from_file(path: &Path) -> Result<Self, failure::Error> {
pub fn from_file(path: &Path) -> Result<Self> {
let mut config: Config = match fs::read_to_string(&path) {
Ok(config_data) => match toml::from_str(&config_data) {
Ok(config) => config,
Err(err) => {
return Err(format_err!("{}: failed to decode: {}", path.display(), err));
bail!("{}: failed to decode: {}", path.display(), err);
}
},
Err(err) => {
return Err(format_err!("{}: failed to read: {}", path.display(), err));
bail!("{}: failed to read: {}", path.display(), err);
}
};
@@ -43,7 +46,7 @@ impl Config {
let mut configs = mem::take(&mut config.include)
.into_iter()
.map(|path| Config::from_file(&config_dir.join(path)))
.collect::<Result<Vec<Config>, failure::Error>>()?;
.collect::<Result<Vec<Config>>>()?;
configs.push(config); // Put ourself last to ensure that it overwrites anything else.
config = configs.remove(0);
+4 -1
View File
@@ -3,11 +3,14 @@
pub enum PackageConfig {
Empty,
Build(String),
// TODO: Sum type
Spec {
version: Option<String>,
git: Option<String>,
path: Option<String>,
}
pkg_path: Option<String>,
},
}
impl Default for PackageConfig {
+16 -20
View File
@@ -21,10 +21,7 @@ enum Buffer<'a> {
impl DiskWrapper {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let disk = OpenOptions::new()
.read(true)
.write(true)
.open(path)?;
let disk = OpenOptions::new().read(true).write(true).open(path)?;
let metadata = disk.metadata()?;
let size = metadata.len();
// TODO: get real block size: disk_metadata.blksize() works on disks but not image files
@@ -71,24 +68,27 @@ impl DiskWrapper {
let remaining = buf_len.checked_sub(i).unwrap();
let len = cmp::min(
remaining,
self.block.len().checked_sub(offset.try_into().unwrap()).unwrap()
self.block
.len()
.checked_sub(offset.try_into().unwrap())
.unwrap(),
);
self.disk.seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?;
self.disk
.seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?;
self.disk.read_exact(&mut self.block)?;
match buf {
Buffer::Read(read) => {
read[i..i.checked_add(len).unwrap()].copy_from_slice(
&self.block[offset..offset.checked_add(len).unwrap()]
);
},
read[i..i.checked_add(len).unwrap()]
.copy_from_slice(&self.block[offset..offset.checked_add(len).unwrap()]);
}
Buffer::Write(write) => {
self.block[offset..offset.checked_add(len).unwrap()].copy_from_slice(
&write[i..i.checked_add(len).unwrap()]
);
self.block[offset..offset.checked_add(len).unwrap()]
.copy_from_slice(&write[i..i.checked_add(len).unwrap()]);
self.disk.seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?;
self.disk
.seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?;
self.disk.write_all(&mut self.block)?;
}
}
@@ -112,12 +112,8 @@ impl Seek for DiskWrapper {
let current: i64 = self.seek.try_into().unwrap();
let end: i64 = self.size.try_into().unwrap();
self.seek = match pos {
SeekFrom::Start(offset) => {
cmp::min(self.size, offset)
},
SeekFrom::End(offset) => {
cmp::max(0, cmp::min(end, end.wrapping_add(offset))) as u64
},
SeekFrom::Start(offset) => cmp::min(self.size, offset),
SeekFrom::End(offset) => cmp::max(0, cmp::min(end, end.wrapping_add(offset))) as u64,
SeekFrom::Current(offset) => {
cmp::max(0, cmp::min(end, current.wrapping_add(offset))) as u64
}
+279 -190
View File
@@ -1,44 +1,31 @@
#[macro_use]
extern crate serde_derive;
extern crate argon2;
extern crate libc;
extern crate liner;
#[macro_use]
extern crate failure;
extern crate pkgutils;
extern crate rand;
extern crate redoxfs;
extern crate syscall;
extern crate termion;
mod config;
mod disk_wrapper;
pub use config::Config;
pub use config::file::FileConfig;
pub use config::package::PackageConfig;
use disk_wrapper::DiskWrapper;
pub use crate::config::file::FileConfig;
pub use crate::config::package::PackageConfig;
pub use crate::config::Config;
use crate::disk_wrapper::DiskWrapper;
use failure::{Error, err_msg};
use rand::{RngCore, rngs::OsRng};
use anyhow::{bail, Result};
use pkgutils::{Package, Repo};
use rand::{rngs::OsRng, RngCore};
use redoxfs::{unmount_path, Disk, DiskIo, FileSystem};
use termion::input::TermRead;
use pkgutils::{Repo, Package};
use std::{
collections::BTreeMap,
env,
fs,
env, fs,
io::{self, Seek, SeekFrom, Write},
path::Path,
process,
sync::mpsc::channel,
time::{SystemTime, UNIX_EPOCH},
thread,
time::{SystemTime, UNIX_EPOCH},
};
pub(crate) type Result<T> = std::result::Result<T, Error>;
pub struct DiskOption<'a> {
pub bootloader_bios: &'a [u8],
pub bootloader_efi: &'a [u8],
@@ -50,10 +37,7 @@ const REMOTE: &'static str = "https://static.redox-os.org/pkg";
fn get_target() -> String {
env::var("TARGET").unwrap_or(
option_env!("TARGET").map_or(
"x86_64-unknown-redox".to_string(),
|x| x.to_string()
)
option_env!("TARGET").map_or("x86_64-unknown-redox".to_string(), |x| x.to_string()),
)
}
@@ -66,7 +50,7 @@ fn hash_password(password: &str) -> Result<String> {
let hash = argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &config)?;
Ok(hash)
} else {
Ok("".to_string())
Ok("".into())
}
}
@@ -88,15 +72,14 @@ fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result<String> {
let confirm_password = stdin.read_passwd(&mut stdout)?;
// Note: Actually comparing two Option<String> values
if confirm_password == password {
Ok(password.unwrap_or("".to_string()))
} else {
Err(err_msg("passwords do not match"))
if confirm_password != password {
bail!("passwords do not match");
}
Ok(password.unwrap_or("".to_string()))
}
//TODO: error handling
fn install_packages<S: AsRef<str>>(config: &Config, dest: &str, cookbook: Option<S>) {
fn install_packages(config: &Config, dest: &str, cookbook: Option<&str>) {
let target = &get_target();
let mut repo = Repo::new(target);
@@ -104,68 +87,127 @@ fn install_packages<S: AsRef<str>>(config: &Config, dest: &str, cookbook: Option
if let Some(cookbook) = cookbook {
let dest_pkg = format!("{}/pkg", dest);
if ! Path::new(&dest_pkg).exists() {
if !Path::new(&dest_pkg).exists() {
fs::create_dir(&dest_pkg).unwrap();
}
for (packagename, package) in &config.packages {
let pkgar_path = format!("{}/{}/repo/{}/{}.pkgar",
env::current_dir().unwrap().to_string_lossy(),
cookbook.as_ref(), target, packagename);
let from_remote = match (config.general.repo_binary, package) {
(Some(true), PackageConfig::Empty) => true,
(Some(true), PackageConfig::Spec { version: None, git: None, path: None }) => true,
(_, PackageConfig::Build(rule)) if rule == "binary" => true,
_ => false
};
if from_remote {
println!("Installing package from remote: {}", packagename);
repo.fetch(&packagename).unwrap().install(dest).unwrap();
} else if Path::new(&pkgar_path).exists() {
println!("Installing package from local repo: {}", packagename);
let public_path = format!("{}/{}/build/id_ed25519.pub.toml",
env::current_dir().unwrap().to_string_lossy(),
cookbook.as_ref());
pkgar::extract(&public_path, &pkgar_path, dest).unwrap();
let pkgar_path = format!(
"{cwd}/{cookbook}/repo/{target}/{packagename}.pkgar",
cwd = env::current_dir().unwrap().to_string_lossy(),
);
let head_path = format!("{}/{}.pkgar_head", dest_pkg, packagename);
pkgar::split(&public_path, &pkgar_path, &head_path, Option::<&str>::None).unwrap();
} else {
println!("Installing package tar.gz from local repo: {}", packagename);
let path = format!("{}/{}/repo/{}/{}.tar.gz",
env::current_dir().unwrap().to_string_lossy(),
cookbook.as_ref(), target, packagename);
Package::from_path(&path).unwrap().install(dest).unwrap();
enum Rule<'a> {
RemotePrebuilt,
LocalPrebuilt { pkg_path: &'a str },
Build,
}
let rule = match (config.general.repo_binary, package) {
(
Some(true),
PackageConfig::Empty
| PackageConfig::Spec {
version: None,
git: None,
path: None,
pkg_path: None,
},
) => {
// prebuilt
Rule::RemotePrebuilt
}
(_, PackageConfig::Build(rule)) if rule == "binary" => Rule::RemotePrebuilt,
(
_,
PackageConfig::Spec {
pkg_path: Some(pkg_path),
..
},
) => Rule::LocalPrebuilt {
pkg_path: &*pkg_path,
},
_ => Rule::Build,
};
match rule {
Rule::LocalPrebuilt { pkg_path } => {
println!(
"Installing package from local pkgar file: {packagename} <- `{pkg_path}`"
);
Package::from_path(pkg_path).unwrap().install(dest).unwrap();
}
Rule::RemotePrebuilt => {
println!("Installing package from remote: {packagename}");
repo.fetch(&packagename).unwrap().install(dest).unwrap();
}
Rule::Build if Path::new(&pkgar_path).exists() => {
println!("Installing package from local repo: {}", packagename);
let public_path = format!(
"{cwd}/{cookbook}/build/id_ed25519.pub.toml",
cwd = env::current_dir().unwrap().to_string_lossy(),
);
pkgar::extract(&public_path, &pkgar_path, dest).unwrap();
let head_path = format!("{dest_pkg}/{packagename}.pkgar_head");
pkgar::split(&public_path, &pkgar_path, &head_path, Option::<&str>::None)
.unwrap();
}
Rule::Build => {
println!("Installing package tar.gz from local repo: {packagename}");
let path = format!(
"{cwd}/{cookbook}/repo/{target}/{packagename}.tar.gz",
cwd = env::current_dir().unwrap().to_string_lossy(),
);
Package::from_path(&path).unwrap().install(dest).unwrap();
}
}
}
} else {
for (packagename, _package) in &config.packages {
println!("Installing package from remote: {}", packagename);
repo.fetch(&packagename).unwrap().install(dest).unwrap();
for (packagename, package) in &config.packages {
let mut package = if let PackageConfig::Spec {
pkg_path: Some(override_path),
..
} = package
{
println!("Installing package from local file: {}", packagename);
Package::from_path(override_path).unwrap()
} else {
println!("Installing package from remote: {}", packagename);
repo.fetch(&packagename).unwrap()
};
package.install(dest).unwrap();
}
}
}
pub fn install_dir<P: AsRef<Path>, S: AsRef<str>>(config: Config, output_dir: P, cookbook: Option<S>) -> Result<()> {
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))
})
($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();
@@ -192,7 +234,8 @@ pub fn install_dir<P: AsRef<Path>, S: AsRef<str>>(config: Config, output_dir: P,
} else if config.general.prompt.unwrap_or(true) {
prompt_password(
&format!("{}: enter password: ", username),
&format!("{}: confirm password: ", username))?
&format!("{}: confirm password: ", username),
)?
} else {
String::new()
};
@@ -209,17 +252,34 @@ pub fn install_dir<P: AsRef<Path>, S: AsRef<str>>(config: Config, output_dir: P,
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)?;
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);
println!("Adding user {username}:");
println!("\tPassword: {password}");
println!("\tUID: {uid}");
println!("\tGID: {gid}");
println!("\tName: {name}");
println!("\tHome: {home}");
println!("\tShell: {shell}");
FileConfig {
path: home.clone(),
@@ -230,7 +290,8 @@ pub fn install_dir<P: AsRef<Path>, S: AsRef<str>>(config: Config, output_dir: P,
uid: Some(uid),
gid: Some(gid),
recursive_chown: true,
}.create(&output_dir)?;
}
.create(&output_dir)?;
if uid >= 1000 {
// Create XDG user dirs
@@ -259,7 +320,8 @@ pub fn install_dir<P: AsRef<Path>, S: AsRef<str>>(config: Config, output_dir: P,
uid: Some(uid),
gid: Some(gid),
recursive_chown: false,
}.create(&output_dir)?;
}
.create(&output_dir)?;
}
FileConfig {
path: format!("{}/.config/user-dirs.dirs", home),
@@ -272,20 +334,22 @@ XDG_PICTURES_DIR="$HOME/Pictures"
XDG_PUBLICSHARE_DIR="$HOME/Public"
XDG_TEMPLATES_DIR="$HOME/Templates"
XDG_VIDEOS_DIR="$HOME/Videos"
"#.to_string(),
"#
.to_string(),
symlink: false,
directory: false,
mode: Some(0o0600),
uid: Some(uid),
gid: Some(gid),
recursive_chown: false,
}.create(&output_dir)?;
}
.create(&output_dir)?;
}
let password = hash_password(&password)?;
passwd.push_str(&format!("{};{};{};{};{};{}\n", username, uid, gid, name, home, shell));
shadow.push_str(&format!("{};{}\n", username, 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]));
}
@@ -312,7 +376,8 @@ XDG_VIDEOS_DIR="$HOME/Videos"
uid: None,
gid: None,
recursive_chown: false,
}.create(&output_dir)?;
}
.create(&output_dir)?;
}
if !shadow.is_empty() {
@@ -325,7 +390,8 @@ XDG_VIDEOS_DIR="$HOME/Videos"
uid: Some(0),
gid: Some(0),
recursive_chown: false,
}.create(&output_dir)?;
}
.create(&output_dir)?;
}
if !groups.is_empty() {
@@ -335,8 +401,8 @@ XDG_VIDEOS_DIR="$HOME/Videos"
use std::fmt::Write;
writeln!(groups_data, "{name};x;{gid};{}", members.join(",")).unwrap();
println!("Adding group {}:", name);
println!("\tGID: {}", gid);
println!("Adding group {name}:");
println!("\tGID: {gid}");
println!("\tMembers: {}", members.join(", "));
}
@@ -350,16 +416,17 @@ XDG_VIDEOS_DIR="$HOME/Videos"
uid: None,
gid: None,
recursive_chown: false,
}.create(&output_dir)?;
}
.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(&Path) -> Result<T>
pub fn with_redoxfs<D, T, F>(disk: D, password_opt: Option<&[u8]>, 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())
@@ -368,35 +435,27 @@ pub fn with_redoxfs<D, T, F>(disk: D, password_opt: Option<&[u8]>, callback: F)
};
if cfg!(not(target_os = "redox")) {
if ! Path::new(&mount_path).exists() {
if !Path::new(&mount_path).exists() {
fs::create_dir(&mount_path)?;
}
}
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)?;
let fs = FileSystem::create(disk, password_opt, ctime.as_secs(), ctime.subsec_nanos())
.map_err(syscall_error)?;
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();
}
);
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();
},
}
};
})
};
@@ -406,10 +465,13 @@ pub fn with_redoxfs<D, T, F>(disk: D, password_opt: Option<&[u8]>, callback: F)
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()),
Err(_) => {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"redoxfs thread did not send a result",
)
.into())
}
};
unmount_path(&mount_path)?;
@@ -423,7 +485,11 @@ pub fn with_redoxfs<D, T, F>(disk: D, password_opt: Option<&[u8]>, callback: F)
res
}
pub fn fetch_bootloaders<S: AsRef<str>>(config: &Config, cookbook: Option<S>, live: bool) -> Result<(Vec<u8>, Vec<u8>)> {
pub fn fetch_bootloaders(
config: &Config,
cookbook: Option<&str>,
live: bool,
) -> Result<(Vec<u8>, Vec<u8>)> {
let bootloader_dir = format!("/tmp/redox_installer_bootloader_{}", process::id());
if Path::new(&bootloader_dir).exists() {
@@ -434,8 +500,10 @@ pub fn fetch_bootloaders<S: AsRef<str>>(config: &Config, cookbook: Option<S>, li
let mut bootloader_config = Config::default();
bootloader_config.general = config.general.clone();
bootloader_config.packages.insert("bootloader".to_string(), PackageConfig::default());
install_packages(&bootloader_config, &bootloader_dir, cookbook.as_ref());
bootloader_config
.packages
.insert("bootloader".to_string(), PackageConfig::default());
install_packages(&bootloader_config, &bootloader_dir, cookbook);
let boot_dir = Path::new(&bootloader_dir).join("boot");
let bios_path = boot_dir.join(if live {
@@ -466,10 +534,10 @@ pub fn fetch_bootloaders<S: AsRef<str>>(config: &Config, cookbook: Option<S>, li
}
//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(&Path) -> Result<T>
pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback: F) -> Result<T>
where
P: AsRef<Path>,
F: FnOnce(&Path) -> Result<T>,
{
let target = get_target();
@@ -478,7 +546,7 @@ pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback
"i686-unknown-redox" => "BOOTIA32.EFI",
"x86_64-unknown-redox" => "BOOTX64.EFI",
_ => {
return Err(format_err!("target '{}' not supported", target));
bail!("target '{target}' not supported");
}
};
@@ -491,11 +559,10 @@ pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback
512 => gpt::disk::LogicalBlockSize::Lb512,
_ => {
// TODO: support (and test) other block sizes
return Err(format_err!("block size {} not supported", block_size));
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;
@@ -506,7 +573,11 @@ pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback
// 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_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
@@ -516,13 +587,17 @@ pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback
// Format and install BIOS partition
{
// Write BIOS bootloader to disk
eprintln!("Write bootloader with size {:#x}", disk_option.bootloader_bios.len());
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 {:#x}", mbr_blocks);
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)?;
@@ -536,39 +611,48 @@ pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback
// 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(),
});
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(),
});
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(),
});
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);
eprintln!("Writing GPT tables: {partitions:#?}");
// Initialize GPT table
gpt_disk.update_partitions(partitions)?;
@@ -581,13 +665,13 @@ pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback
{
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,
)?;
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);
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");
@@ -601,7 +685,11 @@ pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback
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());
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()?;
@@ -609,40 +697,41 @@ pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback
}
// Format and install RedoxFS partition
eprintln!("Installing to RedoxFS partition with size {:#x}", (redoxfs_end - redoxfs_start) * block_size);
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
(redoxfs_end + 1) * block_size,
)?);
with_redoxfs(
disk_redoxfs,
disk_option.password_opt,
callback
)
with_redoxfs(disk_redoxfs, disk_option.password_opt, callback)
}
pub fn install<P, S>(config: Config, output: P, cookbook: Option<S>, live: bool)
-> Result<()> where
P: AsRef<Path>,
S: AsRef<str>,
{
println!("Install {:#?} to {}", config, output.as_ref().display());
fn install_inner(config: Config, output: &Path, cookbook: Option<&str>, live: bool) -> Result<()> {
println!("Install {config:#?} to {}", output.display());
if output.as_ref().is_dir() {
if output.is_dir() {
install_dir(config, output, cookbook)
} else {
let (bootloader_bios, bootloader_efi) = fetch_bootloaders(&config, cookbook.as_ref(), live)?;
let (bootloader_bios, bootloader_efi) = fetch_bootloaders(&config, cookbook, live)?;
let disk_option = DiskOption {
bootloader_bios: &bootloader_bios,
bootloader_efi: &bootloader_efi,
password_opt: None,
efi_partition_size: config.general.efi_partition_size,
};
with_whole_disk(output, &disk_option,
move |mount_path| {
install_dir(config, mount_path, cookbook)
}
)
with_whole_disk(output, &disk_option, move |mount_path| {
install_dir(config, mount_path, cookbook)
})
}
}
pub fn install(
config: Config,
output: impl AsRef<Path>,
cookbook: Option<&str>,
live: bool,
) -> Result<()> {
install_inner(config, output.as_ref(), cookbook, live)
}