@@ -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+
82105def 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+
717763def _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