respaldo del arranque: lista blanca, porque BootCurrent rompía la idempotencia
Dos arranques reales seguidos, sin tocar nada, daban dos respaldos con nombre distinto: boot-27b952c9….json y boot-f999e1e8….json. La promesa de "un arranque que no cambió produce el mismo fichero" era falsa. La causa: el filtro era «empieza por Boot», y eso deja entrar BootCurrent, que dice por dónde arrancó ESTA vez y cambia en cada arranque. Y encima es de SÓLO LECTURA, así que restore habría intentado escribirla. Ahora es lista blanca —BootOrder y Boot#### y nada más—, que también deja fuera BootNext (de un solo uso: restaurarla dispararía un arranque que nadie pidió) y BootOptionSupport (informativa). Ninguna de las tres describe cómo debe arrancar la máquina. Comprobado: cambiando BootCurrent a mano, el respaldo da el mismo fichero y lo dice — "idéntico a uno que ya estaba: el arranque no cambió". Esto sólo se veía ARRANCANDO DOS VECES. Un test del respaldo contra un efivarfs fabricado habría pasado en verde: la variable que rompía la propiedad la pone el firmware, no el código. 80/80 del CLI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
This commit is contained in:
@@ -404,6 +404,27 @@ pub fn first_free_slot(root: &Path) -> io::Result<u16> {
|
||||
Err(io::Error::new(io::ErrorKind::AlreadyExists, "no queda ni un Boot#### libre (0000-FFFF)"))
|
||||
}
|
||||
|
||||
/// ¿Es una variable de **configuración** del arranque, y no estado de la sesión?
|
||||
///
|
||||
/// Lista blanca a propósito: sólo `BootOrder-<guid>` y `Boot####-<guid>`. Un «empieza por Boot» deja
|
||||
/// entrar `BootCurrent` (por dónde arrancó esta vez — cambia en cada arranque y es de sólo lectura),
|
||||
/// `BootOptionSupport` (informativa) y `BootNext` (de un solo uso: restaurarla dispararía un arranque
|
||||
/// que nadie pidió). Ninguna de las tres describe cómo debe arrancar la máquina.
|
||||
pub fn es_variable_de_config(nombre: &str) -> bool {
|
||||
let Some((base, guid)) = nombre.rsplit_once('-') else { return false };
|
||||
// El GUID lleva guiones: `rsplit_once` sólo corta el último trozo, así que se rearma mirando
|
||||
// el sufijo completo.
|
||||
let _ = (base, guid);
|
||||
let Some(resto) = nombre.strip_suffix(&format!("-{GLOBAL_GUID}")) else { return false };
|
||||
if resto == "BootOrder" {
|
||||
return true;
|
||||
}
|
||||
match resto.strip_prefix("Boot") {
|
||||
Some(n) => n.len() == 4 && n.chars().all(|c| c.is_ascii_hexdigit()),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Lista las entradas `Boot####` presentes, en orden numérico, con su descripción ya decodificada.
|
||||
pub fn list_entries(root: &Path) -> io::Result<Vec<(u16, LoadOption)>> {
|
||||
let mut out = Vec::new();
|
||||
@@ -736,6 +757,22 @@ mod tests {
|
||||
std::fs::remove_dir_all(&tmp).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn la_lista_blanca_deja_fuera_el_estado_de_la_sesion() {
|
||||
let g = GLOBAL_GUID;
|
||||
assert!(es_variable_de_config(&format!("BootOrder-{g}")));
|
||||
assert!(es_variable_de_config(&format!("Boot0004-{g}")));
|
||||
assert!(es_variable_de_config(&format!("BootFFFF-{g}")));
|
||||
// Las tres que rompían la idempotencia del respaldo o son de sólo lectura:
|
||||
assert!(!es_variable_de_config(&format!("BootCurrent-{g}")), "cambia en CADA arranque");
|
||||
assert!(!es_variable_de_config(&format!("BootNext-{g}")), "es de un solo uso");
|
||||
assert!(!es_variable_de_config(&format!("BootOptionSupport-{g}")), "informativa");
|
||||
// Y nada de otro namespace de GUID, aunque se llame parecido.
|
||||
assert!(!es_variable_de_config("Boot0004-11111111-2222-3333-4444-555555555555"));
|
||||
assert!(!es_variable_de_config("Boot004-{g}"), "3 dígitos no es un slot");
|
||||
assert!(!es_variable_de_config("BootOrder"), "sin GUID no es una variable EFI");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boot_order_ida_y_vuelta() {
|
||||
let o = vec![0x0001u16, 0x0000, 0x2001];
|
||||
|
||||
@@ -1645,8 +1645,15 @@ fn main() -> anyhow::Result<()> {
|
||||
for ent in std::fs::read_dir(&efivars)? {
|
||||
let ent = ent?;
|
||||
let n = ent.file_name().to_string_lossy().to_string();
|
||||
let interesa = n.starts_with("Boot") && n.contains(efi_boot::GLOBAL_GUID);
|
||||
if !interesa {
|
||||
// LISTA BLANCA, no «empieza con Boot». Medido en dos arranques
|
||||
// seguidos: `BootCurrent` dice por dónde arrancó ESTA vez y cambia
|
||||
// aunque la configuración no haya cambiado, así que el respaldo
|
||||
// salía con un hash distinto cada arranque y la promesa de «un
|
||||
// arranque que no cambió produce el mismo fichero» era falsa. Y
|
||||
// encima es de SÓLO LECTURA: `restore` intentaría escribirla.
|
||||
// Lo mismo `BootOptionSupport` (informativa) y `BootNext` (de un
|
||||
// solo uso: restaurarla dispararía un arranque que nadie pidió).
|
||||
if !efi_boot::es_variable_de_config(&n) {
|
||||
continue;
|
||||
}
|
||||
if let Ok(raw) = std::fs::read(ent.path()) {
|
||||
|
||||
@@ -264,6 +264,18 @@ conocido**. Barato, y es exactamente la forma de takana.
|
||||
> Y `restore` **no escribe por defecto**: hay que pasar `--apply`. La NVRAM es lo único de la máquina
|
||||
> que no se puede rehacer desde el store.
|
||||
>
|
||||
> **Y la promesa de «un arranque que no cambió produce el mismo fichero» empezó siendo falsa.** Dos
|
||||
> arranques reales seguidos, sin tocar nada, dieron `boot-27b952c9….json` y `boot-f999e1e8….json`.
|
||||
> La causa: el filtro era «empieza por `Boot`», y eso deja entrar **`BootCurrent`**, que dice por
|
||||
> dónde arrancó **esta** vez y cambia en cada arranque — además de ser de **sólo lectura**, así que
|
||||
> `restore` habría intentado escribirla. Ahora es lista blanca (`BootOrder` y `Boot####` y nada más),
|
||||
> que también deja fuera `BootNext` (de un solo uso: restaurarla dispararía un arranque que nadie
|
||||
> pidió) y `BootOptionSupport` (informativa). Comprobado: cambiando `BootCurrent` a mano, el respaldo
|
||||
> da el mismo fichero y lo dice — *«idéntico a uno que ya estaba: el arranque no cambió»*.
|
||||
>
|
||||
> Sólo se veía **arrancando dos veces**. Un test del respaldo contra un `efivarfs` fabricado habría
|
||||
> pasado en verde: la variable que rompía la propiedad la pone el firmware, no el código.
|
||||
>
|
||||
> El lector FAT ganó lectura de ficheros para el manifiesto, con su trampa propia: **hay que truncar
|
||||
> al tamaño declarado en el directorio**, no al final del último cluster. Un fichero de 1,5 MB en
|
||||
> clusters de 1 KiB termina con relleno, y hashear el relleno daría un hash distinto al del mismo
|
||||
|
||||
Reference in New Issue
Block a user