From 433aa99660d676837237be7ae5573044f6cc8778 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Tue, 16 Dec 2025 01:41:55 +0700 Subject: [PATCH] Add no_mount option and tidy up flags --- src/bin/installer.rs | 25 ++++++---- src/config/general.rs | 34 ++++++++++++-- src/installer.rs | 107 +++++++++++++++++++++++++++++++++--------- 3 files changed, 133 insertions(+), 33 deletions(-) diff --git a/src/bin/installer.rs b/src/bin/installer.rs index bf14f10303..c2443a1ee6 100644 --- a/src/bin/installer.rs +++ b/src/bin/installer.rs @@ -19,7 +19,8 @@ fn main() { .add_flag(&["filesystem-size"]) .add_flag(&["r", "repo-binary"]) .add_flag(&["l", "list-packages"]) - .add_flag(&["live"]); + .add_flag(&["live"]) + .add_flag(&["no-mount"]); parser.parse(env::args()); // Use pre-built binaries for packages as the default. @@ -127,14 +128,22 @@ fn main() { None }; + if cookbook.is_some() { + config.general.cookbook = cookbook; + } + if parser.found("live") { + config.general.live_disk = Some(true); + } + if parser.found("no-mount") { + config.general.no_mount = Some(true); + } + let write_bootloader = parser.get_opt("write-bootloader"); + if write_bootloader.is_some() { + config.general.write_bootloader = write_bootloader; + } + if let Some(path) = parser.args.first() { - if let Err(err) = redox_installer::install( - config, - path, - cookbook.as_deref(), - parser.found("live"), - parser.get_opt("write-bootloader").as_deref(), - ) { + if let Err(err) = redox_installer::install(config, path) { eprintln!("installer: failed to install: {}", err); process::exit(1); } diff --git a/src/config/general.rs b/src/config/general.rs index b5c736b805..c2a2c6c713 100644 --- a/src/config/general.rs +++ b/src/config/general.rs @@ -1,19 +1,47 @@ #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct GeneralConfig { + /// Specify a path where cookbook exists, all packages will be installed locally + pub cookbook: Option, + /// Allow prompts for missing information such as user password, true by default pub prompt: Option, - // Allow config to specify cookbook recipe or binary package as default + /// Allow config to specify cookbook recipe or binary package as default + /// note: Not read by installer anymore, exists only for legacy reasons pub repo_binary: Option, - pub filesystem_size: Option, //MiB - pub efi_partition_size: Option, //MiB + /// Total filesystem size in MB + pub filesystem_size: Option, + /// EFI partition size in MB, default to 2MB + pub efi_partition_size: Option, + /// Skip disk partitioning, assume whole disk is a partition pub skip_partitions: Option, + /// Set a plain text password to encrypt the disk, or empty string to prompt + pub encrypt_disk: Option, + /// Use live disk for bootloader config, default is false + pub live_disk: Option, + /// If set, write bootloader disk into this path + pub write_bootloader: Option, + /// Use AR to write files instead of FUSE-based mount + /// (bypasses FUSE, but slower and requires namespaced context such as "podman unshare") + pub no_mount: Option, } impl GeneralConfig { + /// Merge two config, "other" is more dominant pub(super) fn merge(&mut self, other: GeneralConfig) { + if let Some(cookbook) = other.cookbook { + self.cookbook = Some(cookbook); + } self.prompt = other.prompt.or(self.prompt); self.repo_binary = other.repo_binary.or(self.repo_binary); self.filesystem_size = other.filesystem_size.or(self.filesystem_size); self.efi_partition_size = other.efi_partition_size.or(self.efi_partition_size); self.skip_partitions = other.skip_partitions.or(self.skip_partitions); + if let Some(encrypt_disk) = other.encrypt_disk { + self.encrypt_disk = Some(encrypt_disk); + } + self.live_disk = other.live_disk.or(self.live_disk); + if let Some(write_bootloader) = other.write_bootloader { + self.write_bootloader = Some(write_bootloader); + } + self.no_mount = other.no_mount.or(self.no_mount); } } diff --git a/src/installer.rs b/src/installer.rs index 12a4d9b240..28d449d72d 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context}; use anyhow::{bail, Result}; use pkg::Library; use rand::{rngs::OsRng, TryRngCore}; -use redoxfs::{unmount_path, Disk, DiskIo, FileSystem}; +use redoxfs::{unmount_path, Disk, DiskIo, FileSystem, BLOCK_SIZE}; use termion::input::TermRead; use crate::config::file::FileConfig; @@ -412,6 +412,67 @@ where res } +pub fn with_redoxfs_ar(mut fs: FileSystem, callback: F) -> Result +where + D: Disk + Send + 'static, + F: FnOnce(&Path) -> Result, +{ + let mount_path = if cfg!(target_os = "redox") { + format!("file.redox_installer_{}", process::id()) + } else { + format!("/tmp/redox_installer_{}", process::id()) + }; + + let res = callback(Path::new(&mount_path)); + + if res.is_ok() { + let _end_block = fs + .tx(|tx| { + // Archive_at root node + redoxfs::archive_at(tx, Path::new(&mount_path), redoxfs::TreePtr::root()) + .map_err(|err| syscall::Error::new(err.raw_os_error().unwrap()))?; + + // Squash alloc log + tx.sync(true)?; + + let end_block = tx.header.size() / BLOCK_SIZE; + /* TODO: Cut off any free blocks at the end of the filesystem + let mut end_changed = true; + while end_changed { + end_changed = false; + + let allocator = fs.allocator(); + let levels = allocator.levels(); + for level in 0..levels.len() { + let level_size = 1 << level; + for &block in levels[level].iter() { + if block < end_block && block + level_size >= end_block { + end_block = block; + end_changed = true; + } + } + } + } + */ + + // Update header + tx.header.size = (end_block * BLOCK_SIZE).into(); + tx.header_changed = true; + tx.sync(false)?; + + Ok(end_block) + }) + .map_err(syscall_error)?; + + // let size = (fs.block + end_block) * BLOCK_SIZE; + // fs.disk.file.set_len(size)?; + } + + fs::remove_dir_all(&mount_path)?; + + res +} + pub fn fetch_bootloaders( config: &Config, cookbook: Option<&str>, @@ -744,42 +805,44 @@ pub fn try_fast_install( Ok(true) } -fn install_inner( - config: Config, - output: &Path, - cookbook: Option<&str>, - live: bool, - write_bootloader: Option<&str>, -) -> Result<()> { +fn install_inner(config: Config, output: &Path) -> Result<()> { println!("Install {config:#?} to {}", output.display()); - + let cookbook = config.general.cookbook.clone(); + let cookbook = cookbook.as_ref().map(|p| p.as_str()); if output.is_dir() { install_dir(config, output, cookbook) } else { + let live = config.general.live_disk.unwrap_or(false); + let password_opt = config.general.encrypt_disk.clone(); + let password_opt = password_opt.as_ref().map(|p| p.as_bytes()); let (bootloader_bios, bootloader_efi) = fetch_bootloaders(&config, cookbook, live)?; - if let Some(write_bootloader) = write_bootloader { + if let Some(write_bootloader) = &config.general.write_bootloader { std::fs::write(write_bootloader, &bootloader_efi)?; } let disk_option = DiskOption { bootloader_bios: &bootloader_bios, bootloader_efi: &bootloader_efi, - password_opt: None, + password_opt: password_opt, 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) - }) + if config.general.no_mount.unwrap_or(false) { + with_redoxfs_ar(fs, move |mount_path| { + install_dir(config, mount_path, cookbook) + }) + } else { + with_redoxfs_mount(fs, move |mount_path| { + install_dir(config, mount_path, cookbook) + }) + } }) } } -pub fn install( - config: Config, - output: impl AsRef, - cookbook: Option<&str>, - live: bool, - write_bootloader: Option<&str>, -) -> Result<()> { - install_inner(config, output.as_ref(), cookbook, live, write_bootloader) + +/// Install RedoxFS into a new disk file, or a sysroot directory. +/// This function assumes all interactive prompts resolved by the caller, +/// so "prompt" option is ignored from this function onward. +pub fn install(config: Config, output: impl AsRef) -> Result<()> { + install_inner(config, output.as_ref()) }