//! La hidratación no escribe fuera de su root, aunque el FHS ya traiga un symlink que salga. //! //! EL FALLO (medido 2026-08-30, antes de `no_escapa`): hidratar es `target_fhs.join(rel)` y //! escribir por ruta. Un artefacto que trae `usr/share/pkg → /algún/lado` deja ese symlink puesto //! —los symlinks se replican literales, y así debe ser— y la hidratación SIGUIENTE escribía //! `usr/share/pkg/archivo` **fuera del root**, devolviendo `Ok(1)`. Con `takana hydrate --into` //! sobre una imagen, eso es escribir en el sistema anfitrión diciendo que todo fue bien. //! //! El corpus no lo disparaba (de 160 symlinks absolutos ninguno apunta a un directorio, y de //! ~42 000 relativos ninguno sale de su artefacto), así que esto es una mina desactivada, no un //! incendio apagado. Por eso el test: lo que no dispara nadie tampoco lo nota nadie. use std::path::Path; use takana_build::hydrate::hydrate; use takana_core::LinkMode; /// Artefacto que trae `usr/share/pkg` como symlink a `destino`. fn artefacto_con_symlink(dir: &Path, destino: &Path) { std::fs::create_dir_all(dir.join("usr/share")).unwrap(); std::os::unix::fs::symlink(destino, dir.join("usr/share/pkg")).unwrap(); } /// Artefacto que trae un fichero DEBAJO de esa misma ruta. fn artefacto_con_fichero(dir: &Path) { std::fs::create_dir_all(dir.join("usr/share/pkg")).unwrap(); std::fs::write(dir.join("usr/share/pkg/archivo"), b"contenido").unwrap(); } #[test] fn un_symlink_del_fhs_que_sale_del_root_no_deja_escribir() { let fuera = tempfile::tempdir().unwrap(); let fhs = tempfile::tempdir().unwrap(); let a = tempfile::tempdir().unwrap(); let b = tempfile::tempdir().unwrap(); artefacto_con_symlink(a.path(), fuera.path()); hydrate(a.path(), fhs.path(), LinkMode::Static, None).unwrap(); artefacto_con_fichero(b.path()); let r = hydrate(b.path(), fhs.path(), LinkMode::Static, None); assert!(r.is_err(), "hidratar a través del symlink tiene que FALLAR, no devolver Ok"); let msg = format!("{}", r.unwrap_err()); assert!(msg.contains("se saldría del root"), "el error tiene que decir qué pasó: {msg}"); assert!( !fuera.path().join("archivo").exists(), "ESCAPÓ: el fichero se escribió fuera del target_fhs" ); } /// El contrapeso: un symlink de directorio que se queda DENTRO tiene que seguir funcionando. /// Es el caso de usr-merge (`/lib → usr/lib`) y el de los temas de iconos (`22@2x → 22`), o sea /// la mayoría de los symlinks de directorio que hay en el corpus. Un guardián que los rompiera /// sería peor que el agujero que tapa. #[test] fn un_symlink_que_se_queda_dentro_sigue_hidratando() { let fhs = tempfile::tempdir().unwrap(); let a = tempfile::tempdir().unwrap(); let b = tempfile::tempdir().unwrap(); // `usr/share/pkg → ../otro` (relativo y dentro), con el destino existiendo. std::fs::create_dir_all(a.path().join("usr/otro")).unwrap(); std::fs::write(a.path().join("usr/otro/.marca"), b"x").unwrap(); std::fs::create_dir_all(a.path().join("usr/share")).unwrap(); std::os::unix::fs::symlink("../otro", a.path().join("usr/share/pkg")).unwrap(); hydrate(a.path(), fhs.path(), LinkMode::Static, None).unwrap(); artefacto_con_fichero(b.path()); let r = hydrate(b.path(), fhs.path(), LinkMode::Static, None); assert!(r.is_ok(), "un symlink que no sale del root no puede bloquear la hidratación: {r:?}"); assert!( fhs.path().join("usr/otro/archivo").exists(), "el fichero tiene que aterrizar donde apunta el symlink, dentro del root" ); }