fix: name a folder or unreadable file in place

A folder or a file the user cannot read at a destination ended --check
and the install on a traceback from the hashing pool. --check now gives
the reason per file, and the install fails that file alone, before any
download for a folder.
This commit is contained in:
Abdessamad Derraz committed 2026-10-23 20:37:32 +02:00
1 parent 4b8e2f9de2
commit 3203c766fc
4 files changed
+53 -5

No files matched your search

+1 -1
View File
@@ -11,7 +11,7 @@
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "6d8af0dbca6697ba71692e5e482c8a16d9e8b9896652fec722d9c84f23bba047"
$defaultInstallSha256 = "c361b511d9550b2115d918a0233115cd9f42920bce881f41395c3d7614be534d"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+18 -3
View File
@@ -4046,9 +4046,18 @@ def _classify_local(entry: dict, dest: Path) -> str:
for name in ("sha256", "sha1")
if entry.get(name)
}
if not dest.is_file():
# A folder at the file's place: --check says so, an install fails
# on that file alone.
return "mismatched"
if not expected:
return "up_to_date"
actual = _digest_file(dest, tuple(expected))
try:
actual = _digest_file(dest, tuple(expected))
except OSError:
# Unreadable (no permission, a read error on a failing card): its
# contents are not known to be the right ones.
return "mismatched"
if all(actual[name] == value for name, value in expected.items()):
return "up_to_date"
return "mismatched"
@@ -4444,6 +4453,9 @@ def _download_one(
if _write_refused(exc):
return f["dest"], False, NOT_WRITABLE
return f["dest"], False, exc.strerror or str(exc)
if dest.is_dir():
# Fetched, it failed only at the rename, on an errno and two paths.
return f["dest"], False, FOLDER_IN_PLACE
if copy is not None:
result = _copy_local(f, bios_path, dest, copy, progress)
@@ -4834,6 +4846,7 @@ READ_ONLY = "file in place is read-only"
# A file in place whose content differs, which could not be moved aside: it
# is left as it is rather than lost.
NOT_KEPT = "file in place could not be kept aside"
FOLDER_IN_PLACE = "a folder sits where this file goes: move it away, then run again"
def _clear_read_only(dest: Path) -> None:
@@ -7539,12 +7552,14 @@ def _wrong_contents(
owners: dict[str, list[dict]] | None = None,
) -> str:
"""Why the file at an entry's place is not the one the list installs."""
if path.is_dir():
return FOLDER_IN_PLACE
try:
size = path.stat().st_size
with open(path, "rb") as handle:
head = handle.read(4)
except OSError:
return ""
except OSError as exc:
return f"could not be read ({exc.strerror or exc}): its contents are unknown"
sha1 = index.sha1(path)
owner = _content_owner(sha1, entry, files, owners)
if owner:
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="6d8af0dbca6697ba71692e5e482c8a16d9e8b9896652fec722d9c84f23bba047"
DEFAULT_INSTALL_SHA256="c361b511d9550b2115d918a0233115cd9f42920bce881f41395c3d7614be534d"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+33
View File
@@ -1501,5 +1501,38 @@ class TheTitleASetupPrints(unittest.TestCase):
self.assertEqual(install.system_aliases(manifest)["Sony PlayStation"], "arcade")
class SomethingElseAtADestination(unittest.TestCase):
"""A folder, or a file this user cannot read, at a destination ended
--check and the install on an IsADirectoryError traceback."""
def prepare(self, repo: FakeRepository) -> None:
(repo.dest / "scph5500.bin").mkdir(parents=True)
unreadable = repo.dest / "scph5501.bin"
unreadable.write_bytes(b"x")
unreadable.chmod(0)
@unittest.skipIf(os.name == "nt" or (hasattr(os, "geteuid") and os.geteuid() == 0),
"a mode of 0 stops no read on Windows or as root")
def test_each_says_why_in_the_check_and_the_install(self):
with FakeRepository() as repo:
self.prepare(repo)
proc = repo.run("--check")
self.assertEqual(proc.returncode, 1, proc.stderr)
self.assertNotIn("Traceback", proc.stderr)
self.assertIn(" wrong hash: scph5500.bin\n a folder sits where this "
"file goes: move it away, then run again", proc.stdout)
self.assertIn(" wrong hash: scph5501.bin\n could not be read "
"(Permission denied)", proc.stdout)
proc = repo.run("--yes")
self.assertEqual(proc.returncode, 1, proc.stderr)
self.assertNotIn("Traceback", proc.stderr)
self.assertIn("failed: scph5500.bin (a folder sits where this file goes",
proc.stdout)
self.assertIn("failed: scph5501.bin (file in place could not be kept aside",
proc.stdout)
self.assertTrue((repo.dest / "scph5500.bin").is_dir())
self.assertIn("gba_bios.bin", repo.installed())
if __name__ == "__main__":
unittest.main()