deb: wipe the staged tree when overlay staging falls back to a copy
On a resumed session whose overlay mount fails (no overlayfs, mount error, non-overlay driver), the fallback used to copy the host tree into the staged tree of the previous attempt: a merge that keeps the old build stamps and resurrects files deleted on the host — a silently half-stale build, exactly what resume must never produce. The driver now reports the failure instead of copying, and the caller wipes the staged build root and re-stages a fresh copy (environment resumable, build artifacts discarded).
This commit is contained in:
+52
-3
@@ -312,7 +312,7 @@ impl Session {
|
||||
|
||||
/// The advisory lock file path: `<sessions root>/<slug>.lock`, outside
|
||||
/// the session root it guards (see [`Session::acquire`]).
|
||||
fn lock_path(&self) -> PathBuf {
|
||||
pub(crate) fn lock_path(&self) -> PathBuf {
|
||||
let name = self
|
||||
.root
|
||||
.file_name()
|
||||
@@ -343,9 +343,33 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wipe the staged tree inside the chroot (the stable build root).
|
||||
///
|
||||
/// Used when overlay staging is unavailable on a resumed session: the
|
||||
/// fallback re-stages a fresh copy, and copying over the previous
|
||||
/// attempt's tree would merge stale build artifacts and resurrect
|
||||
/// host-deleted files into the build.
|
||||
pub fn wipe_staged_tree(&self) -> io::Result<()> {
|
||||
let staged = self.chroot_path().join("tmp/pkh-build");
|
||||
fs::create_dir_all(&staged)?;
|
||||
for entry in fs::read_dir(&staged)?.flatten() {
|
||||
let path = entry.path();
|
||||
let result = if path.is_dir() {
|
||||
fs::remove_dir_all(&path)
|
||||
} else {
|
||||
fs::remove_file(&path)
|
||||
}
|
||||
.or_else(|_| crate::deb::ephemeral::privileged_remove(&path));
|
||||
result
|
||||
.map_err(|e| io::Error::other(format!("cannot clear {}: {e}", staged.display())))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove the whole session tree (unmounting anything mounted under
|
||||
/// it first). The lock file is deleted along with the root; the flock
|
||||
/// stays held by this handle until it drops.
|
||||
/// it first), then unlink the lock file. The caller holds the flock;
|
||||
/// an opener that raced ahead of the unlink failed its lock attempt
|
||||
/// while we held it, and the next one creates a fresh session.
|
||||
pub fn remove(&self) {
|
||||
if let Err(e) = remove_session_dir(&self.root) {
|
||||
log::warn!(
|
||||
@@ -354,6 +378,7 @@ impl Session {
|
||||
e
|
||||
);
|
||||
}
|
||||
let _ = fs::remove_file(self.lock_path());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +674,30 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the lock file at `path` is currently held by a live build
|
||||
/// (flock taken). `Ok(false)` also covers a missing file.
|
||||
pub(crate) fn lock_is_held(path: &Path) -> io::Result<bool> {
|
||||
use std::os::unix::io::AsRawFd;
|
||||
let file = match fs::OpenOptions::new().read(true).open(path) {
|
||||
Ok(file) => file,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(false),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
let taken = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
|
||||
if taken == 0 {
|
||||
// Nobody holds it: release the probe's own lock and report free.
|
||||
unsafe {
|
||||
libc::flock(file.as_raw_fd(), libc::LOCK_UN);
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
let err = io::Error::last_os_error();
|
||||
if err.kind() == io::ErrorKind::WouldBlock {
|
||||
return Ok(true);
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
|
||||
/// Whether the cached chroot tarball the session was bootstrapped from is
|
||||
/// still the one the current bootstrap would use (name, then size/mtime,
|
||||
/// falling back to the recorded SHA-256 when the cheap check drifts).
|
||||
|
||||
Reference in New Issue
Block a user