diff --git a/src/bin/installer.rs b/src/bin/installer.rs index 732e9bee14..4872c43163 100644 --- a/src/bin/installer.rs +++ b/src/bin/installer.rs @@ -157,7 +157,23 @@ fn main() { } if parser.found("filesystem-size") { - println!("{}", config.general.filesystem_size.unwrap_or(0)); + // An explicit `filesystem_size` in the config is an override and is + // honored as-is; otherwise the size is COMPUTED from the actual package + // set (see redox_installer::compute_filesystem_size_mb) — never guessed. + let size = match config.general.filesystem_size { + Some(size) => u64::from(size), + None => { + let cookbook = parser.get_opt("cookbook").unwrap_or_else(|| ".".to_string()); + match redox_installer::compute_filesystem_size_mb(&config, &cookbook) { + Ok(mb) => mb, + Err(err) => { + eprintln!("installer: filesystem-size: {err}"); + process::exit(1); + } + } + } + }; + println!("{size}"); } else if parser.found("list-packages") { // List the packages that should be fetched or built by the cookbook for (packagename, package) in &config.packages { diff --git a/src/installer.rs b/src/installer.rs index dec5d1a6ad..f513593b71 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -130,6 +130,104 @@ fn install_packages(config: &Config, dest: &Path, cookbook: Option<&str>) -> any Ok(()) } +/// Compute the RedoxFS image size (in MiB) needed to hold this config's package +/// set, derived from the ACTUAL package contents — never a hardcoded guess. It +/// walks the transitive package closure (config `[packages]` plus each pkgar's +/// `depends`), does per-file RedoxFS block accounting (`ceil(size/BLOCK_SIZE)` +/// data blocks + one node block per file/dir), adds the record-tree/header +/// structural overhead, and a free-space margin so the resulting live/writable +/// filesystem is functional (a 100%-full RedoxFS cannot be created or used). +/// +/// Target-agnostic: identical logic sizes mini, full, bare, grub, or any config. +/// Reads pkgars from `/repo//` using the repo's public key. +pub fn compute_filesystem_size_mb(config: &Config, cookbook: &str) -> Result { + use pkgar::PackageFile; + use pkgar_core::PackageSrc; + + let target = get_target(); + let repo = Path::new(cookbook).join("repo").join(&target); + let pubkey_path = Path::new(cookbook).join("build").join("id_ed25519.pub.toml"); + let pkey = pkgar_keys::PublicKeyFile::open(&pubkey_path) + .map_err(|e| anyhow::anyhow!("opening public key {}: {:?}", pubkey_path.display(), e))? + .pkey; + + // Transitive closure: explicitly-requested packages + their pkgar `depends`. + let mut stack: Vec = config + .packages + .iter() + .filter_map(|(name, pkg)| match pkg { + PackageConfig::Build(rule) if rule == "ignore" => None, + _ => Some(name.clone()), + }) + .collect(); + let mut seen: BTreeMap = BTreeMap::new(); + + let block = BLOCK_SIZE; // redoxfs block size (4096) + let mut data_blocks: u64 = 0; + let mut nodes: u64 = 0; + let mut missing: Vec = Vec::new(); + + while let Some(name) = stack.pop() { + if seen.insert(name.clone(), ()).is_some() { + continue; + } + // Follow dependencies recorded in the published repo metadata. + let toml_path = repo.join(format!("{name}.toml")); + if let Ok(text) = fs::read_to_string(&toml_path) { + if let Ok(value) = text.parse::() { + if let Some(deps) = value.get("depends").and_then(|d| d.as_array()) { + for dep in deps.iter().filter_map(|d| d.as_str()) { + stack.push(dep.to_string()); + } + } + } + } + // Per-file block accounting from the pkgar itself. + let pkgar_path = repo.join(format!("{name}.pkgar")); + if !pkgar_path.is_file() { + missing.push(name.clone()); + continue; + } + let mut package = PackageFile::new(&pkgar_path, &pkey) + .map_err(|e| anyhow::anyhow!("opening {}: {:?}", pkgar_path.display(), e))?; + let entries = package + .read_entries() + .map_err(|e| anyhow::anyhow!("reading entries of {}: {:?}", pkgar_path.display(), e))?; + for entry in &entries { + data_blocks += entry.size().div_ceil(block); + nodes += 1; // one RedoxFS node per file/dir + } + } + + // Config `[[files]]` payloads (init.d services, filesystem.toml, os-release …). + for file in &config.files { + data_blocks += (file.data.len() as u64).div_ceil(block); + nodes += 1; + } + + if !missing.is_empty() { + eprintln!( + "installer: filesystem-size: warning: {} package(s) missing a pkgar in {} \ + (size may be under-counted): {}", + missing.len(), + repo.display(), + missing.join(", "), + ); + } + + // RedoxFS structural overhead: one node block per entry is already counted in + // `nodes`; the record tree holding those nodes needs ~1 block per 64 entries, + // plus a fixed header/root/allocator allowance (~1 MiB). + let tree_blocks = nodes / 64 + 256; + let content_bytes = (data_blocks + nodes + tree_blocks).saturating_mul(block); + + // Free-space margin so the live/writable image is usable, not 100% full. + let margin = std::cmp::max(content_bytes / 4, 64 * 1024 * 1024); // +25%, min +64 MiB + let total_bytes = content_bytes.saturating_add(margin); + + Ok((total_bytes.div_ceil(1024 * 1024)).max(64)) +} + pub fn install_dir( config: Config, output_dir: impl AsRef,