fix: refuse a cores folder that cannot be listed

This commit is contained in:
Abdessamad Derraz committed 2026-11-05 19:20:14 +01:00
1 parent c0bb3443c0
commit 66dae2a974
6 files changed
+68 -11

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@
# and Invoke-WebRequest throws on a cut transfer before the hash is read:
# the site's build rewrites this line there (scripts/sitebootstrap.py).
$mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
$defaultInstallSha256 = "e818db12a40eb26da591c605351349983861dc35e7cdb98993bb12ee1922d91b"
$defaultInstallSha256 = "aa9407fbbe2d7a7b2ac78407272b96b1d9befb177eea64a5a084c73a0100dee9"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+26
View File
@@ -13636,6 +13636,30 @@ def _missing_dest(dest: Path, ask: bool, named: str = "named with --dest",
return True
def _check_cores_dir(folder: Path) -> None:
"""Refuse a --cores-dir that cannot be listed, saying why: a typo
(core/ for cores/) or a closed folder read as an empty one, and the
run said "none in" and asked for the --cores-dir it was given."""
try:
with os.scandir(folder):
return
except FileNotFoundError:
why = "does not exist"
except NotADirectoryError:
why = "is not a folder"
except PermissionError as exc:
closed = closed_folder(folder)
why = (f"cannot be opened by this user ({exc.strerror or exc})"
if closed is None or closed[0] == folder
else f"cannot be opened: {closed_folder_words(folder, closed)}")
except OSError as exc:
why = f"could not be read ({exc.strerror or exc})"
print(f"Error: --cores-dir {screen_text(str(folder))} {why}.", file=sys.stderr)
print(" Name the folder RetroArch loads cores from (libretro_directory in "
"retroarch.cfg, cores/ beside RetroArch by default).", file=sys.stderr)
sys.exit(1)
def _no_core_found(plat: str, bios_path: Path, searched: list[Path]) -> None:
"""Refuse --installed-cores with no core, and say how to point at them.
@@ -14967,6 +14991,8 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
cores_found: dict[tuple[str, Path], list[str]] = {}
for plat_name, bios_path in platforms:
if plat_name in CORE_PLATFORMS:
if args.cores_dir:
_check_cores_dir(args.cores_dir)
searched = [args.cores_dir] if args.cores_dir else core_dirs(plat_name, bios_path)
cores_found[(plat_name, bios_path)] = installed_cores(searched)
elif args.installed_cores:
+1 -1
View File
@@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins
# curl or wget stop on a cut transfer before the hash is read: the site's
# build rewrites this line there (scripts/sitebootstrap.py).
MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
DEFAULT_INSTALL_SHA256="e818db12a40eb26da591c605351349983861dc35e7cdb98993bb12ee1922d91b"
DEFAULT_INSTALL_SHA256="aa9407fbbe2d7a7b2ac78407272b96b1d9befb177eea64a5a084c73a0100dee9"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+8 -8
View File
@@ -27,8 +27,8 @@
{
"dest": "roms/ports/retrobios/install.py",
"repo_path": "install.py",
"size": 1090483,
"sha256": "e818db12a40eb26da591c605351349983861dc35e7cdb98993bb12ee1922d91b",
"size": 1091637,
"sha256": "aa9407fbbe2d7a7b2ac78407272b96b1d9befb177eea64a5a084c73a0100dee9",
"executable": false
},
{
@@ -71,8 +71,8 @@
"muos": {
"archive": {
"dest": "ARCHIVE/RetroBIOS.muxapp",
"size": 1144110,
"sha256": "5d9347427326038bf925f778d724f87cdafca03e50017123af3cd0eb9939cb7c"
"size": 1145264,
"sha256": "3d17d78050a0fe0181712084cb7bfbc258be08853ffcb7da897628e8a9e9f545"
},
"members": [
{
@@ -92,8 +92,8 @@
{
"name": "RetroBIOS/install.py",
"repo_path": "install.py",
"size": 1090483,
"sha256": "e818db12a40eb26da591c605351349983861dc35e7cdb98993bb12ee1922d91b",
"size": 1091637,
"sha256": "aa9407fbbe2d7a7b2ac78407272b96b1d9befb177eea64a5a084c73a0100dee9",
"executable": false
},
{
@@ -166,8 +166,8 @@
{
"dest": ".config/modules/retrobios/install.py",
"repo_path": "install.py",
"size": 1090483,
"sha256": "e818db12a40eb26da591c605351349983861dc35e7cdb98993bb12ee1922d91b",
"size": 1091637,
"sha256": "aa9407fbbe2d7a7b2ac78407272b96b1d9befb177eea64a5a084c73a0100dee9",
"executable": false
},
{
+31
View File
@@ -4668,6 +4668,37 @@ class RunsAgainstTheRealLists(unittest.TestCase, _LoopbackRun):
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertIn("to the installed cores", proc.stdout)
def test_a_cores_folder_that_cannot_be_listed_says_why(self):
"""A typo (core/ for cores/) or a closed folder read as an empty
one: the run said "none in" and asked for the --cores-dir given."""
missing = self.home / "core"
a_file = self.home / "cores.txt"
a_file.write_text("")
for folder, why in ((missing, "does not exist"), (a_file, "is not a folder")):
with self.subTest(why=why):
proc = _install_run(self.env, "--platform", "retroarch", "--dest",
str(self.home / "s"), "--cores-dir", str(folder),
"--dry-run", "--no-input")
self.assertEqual(proc.returncode, 1)
self.assertIn(f"--cores-dir {folder} {why}", proc.stderr)
self.assertNotIn("none in", proc.stderr)
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
"a folder without read permission is POSIX")
def test_a_closed_cores_folder_says_so(self):
cores = self.home / "closed"
cores.mkdir()
(cores / "handy_libretro.so").write_bytes(b"")
os.chmod(cores, 0o000)
try:
proc = _install_run(self.env, "--platform", "retroarch", "--dest",
str(self.home / "s"), "--cores-dir", str(cores),
"--dry-run", "--no-input")
finally:
os.chmod(cores, 0o755)
self.assertEqual(proc.returncode, 1)
self.assertIn(f"--cores-dir {cores} cannot be opened by this user", proc.stderr)
def test_no_core_found_says_how_to_go_on(self):
proc = _install_run(self.env, "--platform", "retroarch", "--dest",
str(self.home / "s"), "--installed-cores", "--dry-run",
+1 -1
View File
@@ -961,7 +961,7 @@ what the options name, everything by default.
| <span id="option-target"></span>`--target NAME` | Keep only the files the cores of that hardware target need, and the systems they emulate. An unknown target is refused rather than ignored, since carrying on would install everything. On Batocera, Recalbox, ROCKNIX, Lakka and KNULLI the board is read from the system (`batocera.arch`, `recalbox.arch`, `HW_DEVICE`, `LIBREELEC_DEVICE` or `LIBREELEC_PROJECT`, `knulli.arch`) and applied when no `--target` is given, from a pack alone too when it carries the core list per board. On a computer, the board a Batocera card names on its BATOCERA partition (`boot/batocera.board`) is applied the same way, in line mode and on the full screen |
| <span id="option-every-board"></span>`--every-board` | Install the files of every board instead of the one the system names, for a card that moves between devices |
| <span id="option-installed-cores"></span>`--installed-cores` | Keep only what the cores already installed read (RetroArch, Lakka, RetroPie). See [Installed cores](#installed-cores) |
| <span id="option-cores-dir"></span>`--cores-dir PATH` | The folder RetroArch loads its cores from, for `--installed-cores` when it is not found. Implies `--installed-cores` |
| <span id="option-cores-dir"></span>`--cores-dir PATH` | The folder RetroArch loads its cores from, for `--installed-cores` when it is not found. Implies `--installed-cores`. A folder that does not exist or cannot be opened is refused with the reason (code 1) |
| <span id="option-save-dir"></span>`--save-dir PATH` | The frontend's save directory (RetroArch's `savefile_directory`), for the files some cores read there, when the run cannot find it. The core's own folder is still added below it when the frontend sorts saves by core |
| <span id="option-system"></span>`--system NAMES` | Install only these systems. Comma-separated or repeated. `--list-systems` prints the names, `other` included, and the title a system is printed with is taken too. A short name the platform does not use (`gba`, `psx`) is refused with the ids it may stand for, never taken in their place. So is an id the list files under another one: RetroArch keeps `neogeo.zip` under `arcade`, which `--system snk-neogeo` suggests |
| <span id="option-core"></span>`--core NAMES` | Keep the platform's own list and only the extra files these cores load. `--list-cores` prints the names |