mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-29 15:17:20 -05:00
fix: replace a link at a standalone copy's name
A link already at the destination was skipped without a word and counted as "dir not found" while the folder existed, so the emulator kept the file the link pointed at. The link now gives way to the file, written beside it and renamed over the name, and a name leading out of the folder is a named failure.
This commit is contained in:
1 parent
9b1d378d2f
commit
1392f00c8d
5 files changed
+55
-19
No files matched your search
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||||
$defaultInstallSha256 = "007fffece97716aa9e1ed0606a8453f2da8a1262711aa68ca910673956b2b341"
|
$defaultInstallSha256 = "df46e48289c89c5c542095b01c16208ef54696df472685c99327e5022fcd7ab8"
|
||||||
$maximumInstallerBytes = 2MB
|
$maximumInstallerBytes = 2MB
|
||||||
$installerExit = 0
|
$installerExit = 0
|
||||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||||
|
|||||||
+34
-7
@@ -3538,7 +3538,9 @@ def do_standalone_copies(
|
|||||||
|
|
||||||
Returns (copied, skipped, failed): skipped is a target directory that
|
Returns (copied, skipped, failed): skipped is a target directory that
|
||||||
does not exist, failed a copy the system refused (permission, disk
|
does not exist, failed a copy the system refused (permission, disk
|
||||||
full, read-only mount), which used to be counted as "dir not found".
|
full, read-only mount) or a name leading out of the folder, which used
|
||||||
|
to be counted as "dir not found". A link already at a file's name is
|
||||||
|
replaced by the file, never written through.
|
||||||
Each failure is also appended to failures, by absolute path, for the
|
Each failure is also appended to failures, by absolute path, for the
|
||||||
summary to name.
|
summary to name.
|
||||||
"""
|
"""
|
||||||
@@ -3603,14 +3605,23 @@ def do_standalone_copies(
|
|||||||
resolved_dir = target_dir.resolve()
|
resolved_dir = target_dir.resolve()
|
||||||
for src in sources:
|
for src in sources:
|
||||||
dest = target_dir / src.name
|
dest = target_dir / src.name
|
||||||
# A symlink already sitting at the destination would redirect
|
linked = dest.is_symlink()
|
||||||
# the write outside the directory the user opted into, and a
|
# A crafted source name would climb out of the directory.
|
||||||
# crafted source name would climb out of it.
|
if not linked and not _within(dest, resolved_dir):
|
||||||
if dest.is_symlink() or not _within(dest, resolved_dir):
|
print(f" ERROR: {dest}: {OUTSIDE_TARGET}")
|
||||||
skipped += 1
|
failed += 1
|
||||||
|
if failures is not None:
|
||||||
|
failures.append((str(dest), OUTSIDE_TARGET))
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
shutil.copy2(src, dest)
|
if linked:
|
||||||
|
# Writing through a link already at the name would
|
||||||
|
# leave the folder the user opted into. The link
|
||||||
|
# itself gives way to the file.
|
||||||
|
_replace_with_copy(src, dest)
|
||||||
|
print(f" {dest}: link replaced by the file")
|
||||||
|
else:
|
||||||
|
shutil.copy2(src, dest)
|
||||||
copied += 1
|
copied += 1
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
print(f" ERROR: {dest}: {exc.strerror or exc}")
|
print(f" ERROR: {dest}: {exc.strerror or exc}")
|
||||||
@@ -3623,6 +3634,22 @@ def do_standalone_copies(
|
|||||||
return copied, skipped, failed
|
return copied, skipped, failed
|
||||||
|
|
||||||
|
|
||||||
|
OUTSIDE_TARGET = "name leads outside the emulator folder"
|
||||||
|
|
||||||
|
|
||||||
|
def _replace_with_copy(src: Path, dest: Path) -> None:
|
||||||
|
"""Put a copy of src at dest's name without following what is there."""
|
||||||
|
fd, scratch = tempfile.mkstemp(dir=dest.parent, prefix=f".{dest.name}.", suffix=".part")
|
||||||
|
os.close(fd)
|
||||||
|
try:
|
||||||
|
shutil.copy2(src, scratch)
|
||||||
|
os.replace(scratch, dest)
|
||||||
|
except OSError:
|
||||||
|
if os.path.lexists(scratch):
|
||||||
|
os.unlink(scratch)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def format_size(n: int) -> str:
|
def format_size(n: int) -> str:
|
||||||
"""Human-readable file size."""
|
"""Human-readable file size."""
|
||||||
if n < 1024:
|
if n < 1024:
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ esac
|
|||||||
TEMP_INSTALLER=""
|
TEMP_INSTALLER=""
|
||||||
TEMP_DIRECTORY=""
|
TEMP_DIRECTORY=""
|
||||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||||
DEFAULT_INSTALL_SHA256="007fffece97716aa9e1ed0606a8453f2da8a1262711aa68ca910673956b2b341"
|
DEFAULT_INSTALL_SHA256="df46e48289c89c5c542095b01c16208ef54696df472685c99327e5022fcd7ab8"
|
||||||
MAX_INSTALLER_BYTES=2097152
|
MAX_INSTALLER_BYTES=2097152
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
|||||||
+13
-5
@@ -1608,6 +1608,9 @@ class TestStandaloneCopyTargetsAreUntrusted(unittest.TestCase):
|
|||||||
self.assertIsInstance(install._validate_manifest(manifest, "retroarch"), dict)
|
self.assertIsInstance(install._validate_manifest(manifest, "retroarch"), dict)
|
||||||
|
|
||||||
def test_symlinked_destination_is_not_written_through(self):
|
def test_symlinked_destination_is_not_written_through(self):
|
||||||
|
"""A link at the name was skipped in silence and counted as "dir
|
||||||
|
not found" while the folder existed: the emulator kept whatever the
|
||||||
|
link pointed at. The link now gives way to the file."""
|
||||||
bios = Path(_tempdir(self))
|
bios = Path(_tempdir(self))
|
||||||
(bios / "boot.bin").write_text("rom")
|
(bios / "boot.bin").write_text("rom")
|
||||||
target = Path(_tempdir(self))
|
target = Path(_tempdir(self))
|
||||||
@@ -1615,12 +1618,17 @@ class TestStandaloneCopyTargetsAreUntrusted(unittest.TestCase):
|
|||||||
outside.write_text("untouched")
|
outside.write_text("untouched")
|
||||||
(target / "boot.bin").symlink_to(outside)
|
(target / "boot.bin").symlink_to(outside)
|
||||||
|
|
||||||
copied, skipped, _ = install.do_standalone_copies(
|
out = io.StringIO()
|
||||||
self._manifest(str(target)), bios, "linux"
|
with contextlib.redirect_stdout(out):
|
||||||
)
|
copied, skipped, failed = install.do_standalone_copies(
|
||||||
self.assertEqual(copied, 0)
|
self._manifest(str(target)), bios, "linux"
|
||||||
self.assertEqual(skipped, 1)
|
)
|
||||||
|
self.assertEqual((copied, skipped, failed), (1, 0, 0))
|
||||||
self.assertEqual(outside.read_text(), "untouched")
|
self.assertEqual(outside.read_text(), "untouched")
|
||||||
|
self.assertFalse((target / "boot.bin").is_symlink())
|
||||||
|
self.assertEqual((target / "boot.bin").read_text(), "rom")
|
||||||
|
self.assertIn("link replaced", out.getvalue())
|
||||||
|
self.assertEqual(sorted(p.name for p in target.iterdir()), ["boot.bin"])
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(shutil.which("pwsh"), "pwsh not available")
|
@unittest.skipUnless(shutil.which("pwsh"), "pwsh not available")
|
||||||
|
|||||||
+6
-5
@@ -404,9 +404,10 @@ run rather than a file to copy.
|
|||||||
|
|
||||||
Targets are per OS, WSL falling back to the Linux ones. A directory that does
|
Targets are per OS, WSL falling back to the Linux ones. A directory that does
|
||||||
not exist is skipped rather than created, so nothing is copied for an emulator
|
not exist is skipped rather than created, so nothing is copied for an emulator
|
||||||
that is not installed. A destination that is already a symbolic link is skipped
|
that is not installed. A destination that is already a symbolic link is
|
||||||
too: the copy leaves the tree the user opted into, and a link there would
|
replaced by the file rather than written through: a write through the link
|
||||||
redirect the write somewhere else again.
|
would land outside the folder the user opted into. The summary counts each
|
||||||
|
copy as copied, skipped (folder absent) or failed, and names every failure.
|
||||||
|
|
||||||
## Android
|
## Android
|
||||||
|
|
||||||
@@ -478,8 +479,8 @@ user's own layout, and EmuDeck links `bios/shadps4/sys_modules` into shadPS4's
|
|||||||
data directory where the emulator actually reads it. The boundary is lexical,
|
data directory where the emulator actually reads it. The boundary is lexical,
|
||||||
not a resolved-path comparison, which is what keeps that case working.
|
not a resolved-path comparison, which is what keeps that case working.
|
||||||
Standalone copies are stricter, since they write outside the tree the user
|
Standalone copies are stricter, since they write outside the tree the user
|
||||||
selected: a symlink already sitting at the destination is skipped rather than
|
selected: a symlink already sitting at the destination is replaced by the
|
||||||
followed.
|
file rather than followed.
|
||||||
|
|
||||||
## Changing the installer
|
## Changing the installer
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user