deb: do not retain staged trees over the retention size cap
The per-target retention kept the newest five staged trees however large they are: five carried kernel trees are three-digit GiB, most of it compiled objects whose resume value does not scale with their size. A staged tree larger than the retention cap (a user setting, see below) is now dropped at the end of its own build — the record degrades to metadata-only and the next build re-stages from scratch — and the retention sweep also degrades older oversized trees, covering records kept by older pkh versions and interrupted builds. The cap of 0 disables the size rule; the count cap is unchanged. The cap is the first setting of a user configuration file, ~/.config/pkh/config.json (the directory already holds contexts.json): missing file and missing keys mean defaults, a corrupt file is backed up and the defaults apply, and PKH_STAGED_MAXSIZE overrides the file. The default is 20G, the scale where a carried kernel tree stops earning its disk. Every retained tree's size is measured once at retention (the same concurrent, mount-crossing-safe walker the prune report uses) and stored in the record manifest, so sweeps reuse it instead of re-walking the trees on every build.
This commit is contained in:
+102
@@ -0,0 +1,102 @@
|
||||
//! User settings: the optional `config.json` in the pkh configuration
|
||||
//! directory (the same `~/.config/pkh/` that holds `contexts.json`),
|
||||
//! its defaults, and the environment overrides.
|
||||
//!
|
||||
//! A missing file carries the defaults — the file is written only when
|
||||
//! a user creates it, never generated. A file that cannot be read or
|
||||
//! parsed must not take the whole program down: like the context
|
||||
//! configuration, it is backed up to `<path>.bak` (best effort) so a
|
||||
//! later save cannot silently destroy it, and the defaults apply. Every
|
||||
//! field is individually defaulted (`#[serde(default)]`), so any
|
||||
//! hand-written file may carry a subset of the settings.
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// The pkh configuration directory (the one holding `contexts.json`
|
||||
/// and `config.json`).
|
||||
pub fn config_dir() -> Option<PathBuf> {
|
||||
directories::ProjectDirs::from("com", "pkh", "pkh").map(|dirs| dirs.config_dir().to_path_buf())
|
||||
}
|
||||
|
||||
/// The settings file in [`config_dir`].
|
||||
fn config_path() -> Option<PathBuf> {
|
||||
config_dir().map(|dir| dir.join("config.json"))
|
||||
}
|
||||
|
||||
/// The user-adjustable settings, as stored in `config.json`. New fields
|
||||
/// must default — the file always may carry a subset.
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Settings {
|
||||
/// The retention size cap of a staged build tree: trees larger than
|
||||
/// this are not retained after a build (the record degrades to
|
||||
/// metadata-only), and existing oversized trees degrade at the next
|
||||
/// build's retention. A size string (`20G`, `512M`, bytes); `0`
|
||||
/// disables the cap.
|
||||
pub staged_maxsize: String,
|
||||
}
|
||||
|
||||
/// The staged-tree retention cap in bytes: `PKH_STAGED_MAXSIZE` overrides
|
||||
/// the settings file, which overrides the default (20 GiB — the scale
|
||||
/// where a carried kernel tree stops earning its disk).
|
||||
pub fn staged_maxsize() -> u64 {
|
||||
if let Ok(text) = std::env::var("PKH_STAGED_MAXSIZE")
|
||||
&& let Some(bytes) = crate::utils::size::parse_size(&text)
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
file_staged_maxsize()
|
||||
}
|
||||
|
||||
/// The settings-file layer of [`staged_maxsize`]: the stored value
|
||||
/// parsed, the default when unset, unparseable, or the file is missing
|
||||
/// or corrupt (a corrupt file is backed up, as with the context
|
||||
/// configuration).
|
||||
fn file_staged_maxsize() -> u64 {
|
||||
const DEFAULT: u64 = 20 * 1024 * 1024 * 1024;
|
||||
let Some(path) = config_path() else {
|
||||
return DEFAULT;
|
||||
};
|
||||
let Ok(text) = std::fs::read_to_string(&path) else {
|
||||
return DEFAULT;
|
||||
};
|
||||
let settings: Settings = match serde_json::from_str(&text) {
|
||||
Ok(settings) => settings,
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Settings file {} is corrupt ({e}); using the defaults",
|
||||
path.display()
|
||||
);
|
||||
backup(&path);
|
||||
return DEFAULT;
|
||||
}
|
||||
};
|
||||
crate::utils::size::parse_size(&settings.staged_maxsize).unwrap_or_else(|| {
|
||||
log::warn!(
|
||||
"Settings file {} carries an unparseable staged_maxsize \
|
||||
'{}'; using the default",
|
||||
path.display(),
|
||||
settings.staged_maxsize
|
||||
);
|
||||
DEFAULT
|
||||
})
|
||||
}
|
||||
|
||||
/// Back up a corrupt settings file next to itself (best effort), so a
|
||||
/// later save cannot silently destroy its content.
|
||||
fn backup(path: &std::path::Path) {
|
||||
let mut os = path.as_os_str().to_os_string();
|
||||
os.push(".bak");
|
||||
let backup_path = PathBuf::from(os);
|
||||
match std::fs::copy(path, &backup_path) {
|
||||
Ok(_) => log::warn!(
|
||||
"Corrupt settings file backed up to {}",
|
||||
backup_path.display()
|
||||
),
|
||||
Err(e) => log::warn!(
|
||||
"Could not back up corrupt settings file to {}: {e}",
|
||||
backup_path.display()
|
||||
),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user