Skip to content

Commit ec6ca85

Browse files
committed
Read Gradle copies through the FIFO-safe opener
The superseded-copy probe read installed files with a bare read, so a FIFO or device planted in a Gradle cache directory would block rollback and remove forever. It now uses read_regular_to_bytes like the neighbouring Gradle checks; a non-regular file is refused and counts as possibly patched, so the copy fails as before. Assisted-by: Claude Code:claude-opus-5-5
1 parent 1494f42 commit ec6ca85

1 file changed

Lines changed: 26 additions & 1 deletion

File tree

‎crates/socket-patch-cli/src/commands/rollback.rs‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2899,7 +2899,10 @@ async fn holds_patched_bytes(target: &CopyTarget, files: &HashMap<String, PatchF
28992899
{
29002900
return true;
29012901
}
2902-
match tokio::fs::read(target.dir.join(rel)).await {
2902+
// FIFO-safe: a FIFO or device planted at the leaf is refused, not
2903+
// opened (a bare read would block forever), and counts as possibly
2904+
// patched below.
2905+
match socket_patch_core::utils::fs::read_regular_to_bytes(&target.dir.join(rel)).await {
29032906
Ok(bytes) => {
29042907
if socket_patch_core::hash::git_sha256::compute_git_sha256_from_bytes(&bytes)
29052908
== info.after_hash
@@ -3477,6 +3480,28 @@ mod tests {
34773480
}
34783481
}
34793482

3483+
#[cfg(unix)]
3484+
#[tokio::test]
3485+
async fn superseded_skip_never_blocks_on_a_fifo() {
3486+
// A FIFO where the record's file should be is unverifiable: the
3487+
// check must refuse it, not block in open(2), and must not skip.
3488+
let (tmp, target, files) = superseded_copy(b"patched by B");
3489+
std::fs::remove_file(tmp.path().join("index.js")).unwrap();
3490+
let made = std::process::Command::new("mkfifo")
3491+
.arg(tmp.path().join("index.js"))
3492+
.status()
3493+
.expect("run mkfifo");
3494+
assert!(made.success());
3495+
let result = refused("gradle_rollback_hash_mismatch: the before-blob for x does not hash");
3496+
let skip = tokio::time::timeout(
3497+
std::time::Duration::from_secs(10),
3498+
superseded_record_skip(&target, &result, &files, &superseded_map()),
3499+
)
3500+
.await
3501+
.expect("the patched-bytes probe must not block on a FIFO");
3502+
assert!(skip.is_none());
3503+
}
3504+
34803505
#[tokio::test]
34813506
async fn superseded_skip_leaves_other_failures_and_records_alone() {
34823507
let (_tmp, target, files) = superseded_copy(b"patched by B");

0 commit comments

Comments
 (0)