From 2a243985c0cdc6a433f7151ca1ca4c66a7b3e342 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 11:43:03 +0100 Subject: [PATCH] fix: rename extracted BIOS files into place --- scripts/download.sh | 20 +++++++++++++++++++- tests/test_download.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/scripts/download.sh b/scripts/download.sh index 9800583e8..bc2c79afd 100755 --- a/scripts/download.sh +++ b/scripts/download.sh @@ -191,9 +191,27 @@ EOF done echo "Extracting to ${dest}/..." + # unzip -o unlinks a file and writes its replacement under the final + # name, so a run stopped mid-file left a partial BIOS. Members land in + # the staging folder, on the same filesystem, and each is renamed into + # place: a file in the BIOS folder is the old one or the new one. + local extracted="${staging}/extracted" for file in "${staging}"/*.zip; do - unzip -o -q "$file" -d "$dest" + unzip -o -q "$file" -d "$extracted" done + local target + target=$(cd "$dest" && pwd) + ( + cd "$extracted" + find . -type f -exec sh -c ' + target="$1" + shift + for member; do + mkdir -p "$target/$(dirname "$member")" + mv -f "$member" "$target/$member" + done + ' sh "$target" {} + + ) cleanup_staging trap - EXIT diff --git a/tests/test_download.py b/tests/test_download.py index 0b57a1daa..154f780ca 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -575,6 +575,35 @@ class TestDownloadShell(DownloaderCase): self.assertNotIn("unbound variable", proc.stderr) self.assertEqual(list(self.dest.iterdir()), []) + def test_an_interrupted_extraction_keeps_the_installed_file(self): + """unzip -o unlinks a file and writes the new one under its final + name: a run killed mid-file left a partial BIOS where a valid one + was. Extraction now writes beside the folder and renames.""" + shim = Path(_tempdir(self)) + (shim / "unzip").write_text( + "#!/bin/sh\n" + 'for target; do :; done\n' + 'mkdir -p "$target/bios"\n' + 'printf partial > "$target/bios/scph5501.bin"\n' + "exit 137\n" + ) + (shim / "unzip").chmod(0o755) + installed = self.dest / "bios" / "scph5501.bin" + installed.parent.mkdir(parents=True) + installed.write_bytes(b"valid") + env = self.env() + env["PATH"] = f"{shim}{os.pathsep}{env['PATH']}" + proc = subprocess.run( + ["bash", str(SHELL), self.platform, str(self.dest)], + env=env, capture_output=True, text=True, timeout=120, + ) + self.assertNotEqual(proc.returncode, 0) + self.assertEqual(installed.read_bytes(), b"valid") + self.assertEqual( + sorted(p.name for p in self.dest.iterdir()), ["bios"], + "staging left behind", + ) + def test_two_runs_into_one_folder_both_finish(self): """Both runs staged in `/.retrobios-download` and the second emptied it while the first was still writing its parts."""