mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-25 05:09:03 -05:00
fix: refuse a detected folder under a closed one
A system_directory read from retroarch.cfg under a folder this user cannot open ended the run, and the interface before its first screen, on a traceback. It now gets the refusal --dest gets. On a handheld, an idle close on an error screen shows that error for a few seconds instead of waiting for a button that cannot come.
This commit is contained in:
1 parent
a94050c1a3
commit
f51bc069d5
7 files changed
+84
-12
No files matched your search
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "aa47489286d9f69a13b5e526fbf573eba9c1edc76ece9ca403b2a1952090c89a"
|
||||
$defaultInstallSha256 = "dcf0c308a5a3176b66360e196d10b2ac678bda7f9245b22bc0cf43b0459a7d4d"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+20
-9
@@ -8767,6 +8767,19 @@ def _open_from_folders(folders: list[Path]) -> None:
|
||||
other_drives=[str(drive) for drive in index.other_drives])
|
||||
|
||||
|
||||
def _refuse_closed(dest: Path, named: str) -> None:
|
||||
"""End the run, with its name, on a folder above dest this user cannot open."""
|
||||
closed = closed_folder(dest)
|
||||
if closed is None:
|
||||
return
|
||||
message = closed_folder_words(dest, closed)
|
||||
EVENTS.emit("error", stage="dest", reason="unreadable", platform="", message=message)
|
||||
print(f"Error: {screen_text(message)}. Give this user access to it, run as "
|
||||
f"its owner, or choose another folder than the one {named}.",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _missing_dest(dest: Path, ask: bool, named: str = "named with --dest") -> None:
|
||||
"""Say that a --dest folder does not exist before anything goes there.
|
||||
|
||||
@@ -8777,14 +8790,7 @@ def _missing_dest(dest: Path, ask: bool, named: str = "named with --dest") -> No
|
||||
at a terminal the run asks first, and the answer defaults to no. A
|
||||
folder above it this user cannot open ends the run with its name.
|
||||
"""
|
||||
closed = closed_folder(dest)
|
||||
if closed is not None:
|
||||
message = closed_folder_words(dest, closed)
|
||||
EVENTS.emit("error", stage="dest", reason="unreadable", platform="", message=message)
|
||||
print(f"Error: {screen_text(message)}. Give this user access to it, run as "
|
||||
f"its owner, or choose another folder than the one {named}.",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
_refuse_closed(dest, named)
|
||||
if dest.exists():
|
||||
return
|
||||
nearest = dest.absolute()
|
||||
@@ -9207,6 +9213,11 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
|
||||
if args.dest is not None and not listing:
|
||||
_missing_dest(args.dest, interactive and not (args.check or args.dry_run or args.yes))
|
||||
elif not listing:
|
||||
# A system_directory under /root read from retroarch.cfg, run without
|
||||
# sudo: the case probe raised PermissionError before any line.
|
||||
for _name, path in platforms:
|
||||
_refuse_closed(path, "detected here")
|
||||
|
||||
total_downloaded = 0
|
||||
total_up_to_date = 0
|
||||
@@ -12495,7 +12506,7 @@ def _install_kind(path: Path) -> str:
|
||||
("/snap/", "Snap")):
|
||||
if marker in text:
|
||||
return word
|
||||
if (path.parent / "retroarch.cfg").is_file():
|
||||
if _is_file(path.parent / "retroarch.cfg"):
|
||||
return "portable"
|
||||
return ""
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ esac
|
||||
TEMP_INSTALLER=""
|
||||
TEMP_DIRECTORY=""
|
||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
DEFAULT_INSTALL_SHA256="aa47489286d9f69a13b5e526fbf573eba9c1edc76ece9ca403b2a1952090c89a"
|
||||
DEFAULT_INSTALL_SHA256="dcf0c308a5a3176b66360e196d10b2ac678bda7f9245b22bc0cf43b0459a7d4d"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -876,6 +876,12 @@ rb_here() {
|
||||
# Ended by Start + Select or a signal: the player asked to leave, and no
|
||||
# button may be left to close a message.
|
||||
:
|
||||
elif [ "$status" != "0" ] && [ "$(head -n 1 "$RB_STATE/tui.err" 2>/dev/null)" = "No button pressed, closing." ]; then
|
||||
# Closed for want of a button: the mapper never opened the pad, so no A
|
||||
# is coming. The error it closed is the next line, shown for
|
||||
# RB_DEAD_WAIT seconds.
|
||||
RB_MESSAGE_WAIT="$RB_DEAD_WAIT" rb_message \
|
||||
"$(sed -n 2p "$RB_STATE/tui.err") The messages are in $RB_LOG."
|
||||
elif [ "$status" != "0" ] && [ -s "$RB_STATE/tui.err" ]; then
|
||||
# The first line of the farewell: the error screen it closes was read.
|
||||
rb_message "$(head -n 1 "$RB_STATE/tui.err") The messages are in $RB_LOG."
|
||||
|
||||
@@ -484,6 +484,10 @@ if os.environ.get("FAKE_HOLD"):
|
||||
note("ui.ready")
|
||||
time.sleep(60)
|
||||
sys.exit(0)
|
||||
if os.environ.get("FAKE_IDLE_ERROR"):
|
||||
# RETROBIOS_IDLE_EXIT on an error screen: the idle line, then the error.
|
||||
print("No button pressed, closing.", file=sys.stderr)
|
||||
print("Error: " + os.environ["FAKE_IDLE_ERROR"], file=sys.stderr)
|
||||
if os.environ.get("FAKE_ERROR"):
|
||||
print("Error: " + os.environ["FAKE_ERROR"], file=sys.stderr)
|
||||
sys.exit(int(os.environ.get("FAKE_CODE", "0")))
|
||||
@@ -1197,6 +1201,19 @@ class ThePortMasterPort(unittest.TestCase):
|
||||
device.assert_mapper_gone()
|
||||
device.assert_console_restored()
|
||||
|
||||
def test_an_idle_close_on_an_error_names_the_error_at_once(self):
|
||||
"""The interface closed for want of a button: the mapper never
|
||||
opened the pad, so no A can close a message. The error it closed is
|
||||
named, for RB_DEAD_WAIT seconds, not the idle line for 300."""
|
||||
device = self.device()
|
||||
proc = device.run(["bash", self.script], FAKE_IDLE_ERROR="could not reach the file list",
|
||||
FAKE_CODE="1", RB_MESSAGE_WAIT="300", RB_DEAD_WAIT="1")
|
||||
self.assertEqual(proc.returncode, 1)
|
||||
self.assertLess(device.elapsed, 10)
|
||||
self.assertIn("could not reach the file list", device.screen)
|
||||
self.assertNotIn("No button pressed", device.screen)
|
||||
device.assert_console_restored()
|
||||
|
||||
def test_start_select_before_the_interface_handles_signals(self):
|
||||
"""A SIGTERM before run_tui installs its handlers ends Python on the
|
||||
default action, status 143."""
|
||||
|
||||
@@ -595,6 +595,44 @@ class AFolderThisUserCannotEnter(unittest.TestCase):
|
||||
self.assertIn(str(lock), errors[0]["message"])
|
||||
|
||||
|
||||
class ADetectedFolderUnderAClosedOne(unittest.TestCase):
|
||||
"""retroarch.cfg naming a system_directory under a folder this user
|
||||
cannot open, with no --dest: the case probe raised PermissionError and
|
||||
the run, and the interface before its first screen, ended on a
|
||||
traceback. The refusal --dest gets is the one detection gets."""
|
||||
|
||||
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
|
||||
"a folder without search permission is POSIX")
|
||||
def test_it_is_refused_in_words(self):
|
||||
with FakeRepository() as repo:
|
||||
home = repo.root / "home"
|
||||
lock = repo.root / "lock"
|
||||
(home / ".config" / "retroarch").mkdir(parents=True)
|
||||
lock.mkdir()
|
||||
(home / ".config" / "retroarch" / "retroarch.cfg").write_text(
|
||||
f'system_directory = "{lock / "system"}"\n')
|
||||
env = {**repo.env(), "HOME": str(home), "XDG_CONFIG_HOME": str(home / ".config"),
|
||||
"RETROBIOS_OS": "linux"}
|
||||
command = [sys.executable, str(REPO_ROOT / "install.py"), "--platform",
|
||||
"retroarch", "--no-input"]
|
||||
os.chmod(lock, 0o000)
|
||||
try:
|
||||
runs = {name: subprocess.run(command + extra, env=env, capture_output=True,
|
||||
text=True, timeout=60, stdin=subprocess.DEVNULL)
|
||||
for name, extra in (("check", ["--check"]), ("install", []))}
|
||||
kinds = install.tell_apart([("retroarch", lock / "system", "RetroArch"),
|
||||
("retroarch", repo.dest, "RetroArch")])
|
||||
finally:
|
||||
os.chmod(lock, 0o755)
|
||||
for name, proc in runs.items():
|
||||
with self.subTest(run=name):
|
||||
self.assertNotIn("Traceback", proc.stderr)
|
||||
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
|
||||
self.assertIn(f"this user cannot open {lock} (Permission denied)",
|
||||
proc.stderr)
|
||||
self.assertEqual(len(kinds), 2)
|
||||
|
||||
|
||||
class OutputToAFileAsksNothing(unittest.TestCase):
|
||||
"""`install.py > log.txt` at a terminal: the menu went into the file
|
||||
and the run waited on an answer to a prompt nobody could see."""
|
||||
|
||||
+1
-1
@@ -1387,7 +1387,7 @@ PowerShell as Administrator only for a protected folder.
|
||||
|
||||
When a folder above the BIOS folder cannot be opened at all (`/root/...` run
|
||||
without `sudo`, a card mounted for another user), the run stops before it
|
||||
starts: `this user cannot open /root/.config (Permission denied), which holds
|
||||
starts: `this user cannot open /root (Permission denied), which holds
|
||||
/root/.config/retroarch/system`. The full-screen interface says the same on
|
||||
the folder screen and asks for another folder.
|
||||
|
||||
|
||||
Reference in new issue
Block a user