Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,17 @@ def start_rom(args: argparse.Namespace, maxnbplayers: int, rom: Path, original_r

# run the emulator
_evmapy_instance = evmapy(systemName, system.config.emulator, effectiveCore, original_rom, player_controllers, guns)
# change directory if wanted. it is restored on the way out: a rom on a
# squashfs is unmounted below, and a process sitting in a mount, even
# this one, keeps it busy
executionDirectory = generator.executionDirectory(system.config, rom)

with (
_evmapy_instance,
set_hotkeygen_context(generator, system)
set_hotkeygen_context(generator, system),
contextlib.chdir(executionDirectory) if executionDirectory is not None
else contextlib.nullcontext()
):
# change directory if wanted
executionDirectory = generator.executionDirectory(system.config, rom)
if executionDirectory is not None:
os.chdir(executionDirectory)

# Initialize overlay process-tracking and check for active MangoHUD
bezel_proc = None
bezel_log_file = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from ..batoceraPaths import mkdir_if_not_exists
from ..exceptions import BatoceraException
from .squashfs import unmount

if TYPE_CHECKING:
from collections.abc import Generator
Expand All @@ -19,15 +20,12 @@


def _unmount_and_remove(mount_point: Path):
if mount_point.is_mount():
result = subprocess.run(["umount", str(mount_point)], capture_output=True, text=True)
if result.returncode != 0:
_logger.error("failed unmounting '%s' (rc=%d) because %s",
mount_point, result.returncode, result.stderr.strip())

# Skip the follow-on removal if the umount failed because it might still
# be connected to the ROM's save area (system crashing or bad state?).
return
if mount_point.is_mount() and not unmount(mount_point):
_logger.error("failed unmounting '%s'", mount_point)

# Skip the follow-on removal if the umount failed because it might still
# be connected to the ROM's save area (system crashing or bad state?).
return

shutil.rmtree(mount_point, ignore_errors=True)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import contextlib
import logging
import subprocess
from contextlib import contextmanager
Expand All @@ -17,6 +18,21 @@
_SQUASHFS_DIR: Final = Path("/var/run/squashfs/")


def unmount(mount_point: Path, /) -> bool:
result = subprocess.run(["umount", str(mount_point)], capture_output=True, text=True)

if result.returncode == 0:
return True

# a game that was killed rather than closed can leave a process holding the mount
# while it dies: detach it anyway, the kernel drops it once the last one lets go,
# leaving it mounted would keep the rom busy until the next reboot
_logger.warning("'%s' is busy, detaching it lazily because %s",
mount_point, result.stderr.strip())

return subprocess.run(["umount", "-l", str(mount_point)], capture_output=True, text=True).returncode == 0


@contextmanager
def mount_squashfs(rom: Path, /) -> Generator[Path]:
_logger.debug("mount_squashfs(%s)", rom)
Expand All @@ -27,14 +43,22 @@ def mount_squashfs(rom: Path, /) -> Generator[Path]:
# first, try to clean an empty remaining directory (for example because of a crash)
if mount_point.exists() and mount_point.is_dir():
_logger.debug("squashfs_rom: %s already exists", mount_point)
# try to remove an empty directory, else, run the directory, ignoring the .squashfs

# a previous run may have left the rom mounted here, after a crash or an unmount
# that was refused: take it down rather than running the game off a stale mount,
# which isn't necessarily even the same rom
if mount_point.is_mount():
_logger.debug("squashfs_rom: %s is still mounted, unmounting it first", mount_point)
unmount(mount_point)

# whatever is left is not ours to run the game off: it isn't necessarily even
# the same rom, and it would never be unmounted either
try:
mount_point.rmdir()
except (FileNotFoundError, OSError):
_logger.debug("squashfs_rom: failed to rmdir %s", mount_point)
yield mount_point
# No cleanup is necessary
return
except FileNotFoundError:
pass
except OSError as e:
raise BatoceraException(f"Unable to clean the mount point {mount_point}") from e

# ok, the base directory doesn't exist, let's create it and mount the squashfs on it
mount_point.mkdir()
Expand Down Expand Up @@ -66,10 +90,10 @@ def mount_squashfs(rom: Path, /) -> Generator[Path]:
_logger.debug("mount_squashfs: cleaning up %s", mount_point)

# unmount
return_code = subprocess.call(["umount", mount_point])
if return_code != 0:
if not unmount(mount_point):
_logger.debug("mount_squashfs: unmounting %s failed", mount_point)
raise BatoceraException(f"Unable to unmount the file {mount_point}")

# cleaning the empty directory
mount_point.rmdir()
# cleaning the empty directory, a lazily detached mount may still hold it
with contextlib.suppress(OSError):
mount_point.rmdir()