Skip to content

Commit daf2172

Browse files
committed
fix: stale-proof installer lock + pacman lock precheck
- lock steal: if the recorded pid is dead (SIGKILL/OOM skips finally, tmpfs keeps the lock till reboot), take the lock. live pid still refused, error now names the pid + notes the live ISO auto-opens the installer at login. - pacman db.lck precheck before pacstrap: live holder gives a clear error, stale lock is removed with a log line.
1 parent 45784c8 commit daf2172

1 file changed

Lines changed: 65 additions & 0 deletions

File tree

‎installer.py‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,31 @@ def set_fallback_mirrors(lfn):
7979
lfn(f"[yellow]could not write mirrorlist: {e}[/]")
8080

8181

82+
_PACMAN_LOCK = "/var/lib/pacman/db.lck"
83+
84+
def _pacman_lock_check(lfn):
85+
"""fail fast with a clear message instead of dying cryptically in pacstrap."""
86+
import shutil
87+
if not os.path.exists(_PACMAN_LOCK):
88+
return
89+
holder = None
90+
if shutil.which("fuser"):
91+
try:
92+
r = subprocess.run(["fuser", _PACMAN_LOCK], capture_output=True, text=True, timeout=10)
93+
holder = (r.stdout or r.stderr or "").strip()
94+
except Exception:
95+
holder = None
96+
if holder:
97+
raise RuntimeError(f"pacman database is locked (held by: {holder}) — another pacman/installer is running")
98+
# lock file with no live holder: stale, remove and proceed
99+
lfn("[yellow]stale pacman lock found, removing it...[/]")
100+
try:
101+
os.remove(_PACMAN_LOCK)
102+
except OSError as e:
103+
raise RuntimeError(f"cannot clear stale pacman lock: {e}")
104+
82105
def pacstrap_with_fallback(target, pkgs, abort_flag, lfn, timeout=900):
106+
_pacman_lock_check(lfn)
83107
try:
84108
abortable_run(["pacstrap", "-K", target] + pkgs, abort_flag, timeout=timeout, log_fn=lfn)
85109
return True
@@ -714,7 +738,45 @@ def cleanup_mounts(target):
714738

715739
_INSTALL_LOCK = "/tmp/mochiinstall.lock"
716740

741+
def _lock_owner():
742+
"""pid recorded in the lock file, or None if unreadable."""
743+
try:
744+
with open(_INSTALL_LOCK) as f:
745+
return int(f.read().strip())
746+
except (OSError, ValueError):
747+
return None
748+
749+
def _pid_alive(pid):
750+
"""true if a process with this pid exists (no signal sent)."""
751+
if not pid or pid <= 0:
752+
return False
753+
try:
754+
os.kill(pid, 0)
755+
return True
756+
except ProcessLookupError:
757+
return False
758+
except PermissionError:
759+
return True # exists, just not ours
760+
except OSError:
761+
return False
762+
717763
def _acquire_lock():
764+
try:
765+
fd = os.open(_INSTALL_LOCK, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644)
766+
os.write(fd, str(os.getpid()).encode())
767+
os.close(fd)
768+
return True
769+
except FileExistsError:
770+
pass
771+
# lock exists — steal it only if the owner is dead (crash/kill -9
772+
# skips the finally that releases it; tmpfs keeps it until reboot)
773+
owner = _lock_owner()
774+
if owner is not None and _pid_alive(owner):
775+
return False
776+
try:
777+
os.remove(_INSTALL_LOCK)
778+
except OSError:
779+
return False
718780
try:
719781
fd = os.open(_INSTALL_LOCK, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644)
720782
os.write(fd, str(os.getpid()).encode())
@@ -737,6 +799,9 @@ def do_install(target="/mnt/mochios", config=None, log_fn=None, abort_flag=None)
737799
config = {}
738800

739801
if not _acquire_lock():
802+
owner = _lock_owner()
803+
if owner:
804+
raise RuntimeError(f"another installer instance is already running (pid {owner}) — check your taskbar, the live ISO auto-opens it at login")
740805
raise RuntimeError("another installer instance is already running")
741806

742807
mounts_cleanup_needed = False

0 commit comments

Comments
 (0)