[PATCH] Only allow one anaconda instance (#1146735)

David Shea dshea at redhat.com
Wed Oct 8 14:04:32 UTC 2014


If the PID file is stale, remove it and retry.  Display a graphical
error dialog if we're in the sort of environment that can display
graphical dialogs early on, otherwise just print a message and bomb out.
---
 anaconda | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 80 insertions(+), 4 deletions(-)

diff --git a/anaconda b/anaconda
index beeaabd..5e5bd25 100755
--- a/anaconda
+++ b/anaconda
@@ -44,7 +44,7 @@ if ("debug=1" in proc_cmdline) or ("debug" in proc_cmdline):
     cov.start()
 
 
-import atexit, sys, os, time, signal
+import atexit, sys, os, time, signal, stat, errno
 
 def exitHandler(rebootData, storage):
     # Clear the list of watched PIDs.
@@ -81,6 +81,10 @@ def exitHandler(rebootData, storage):
         uninhibit_screensaver(anaconda.dbus_session_connection, anaconda.dbus_inhibit_id)
         anaconda.dbus_inhibit_id = None
 
+    # Clean up the PID file
+    if pidfile_created:
+        os.unlink(pidfile_path)
+
     if not flags.imageInstall and not flags.livecdInstall \
        and not flags.dirInstall:
         from pykickstart.constants import KS_SHUTDOWN, KS_WAIT
@@ -841,9 +845,81 @@ if __name__ == "__main__":
     # make sure we have /var/log soon, some programs fail to start without it
     iutil.mkdirChain("/var/log")
 
-    pidfile = open("/var/run/anaconda.pid", "w")
-    pidfile.write("%s\n" % (os.getpid(),))
-    del pidfile
+    # Share these with the exit handler, installed later
+    pidfile_path = '/var/run/anaconda.pid'
+    pidfile_created = False
+
+    # Look for a stale pid file
+    try:
+        with open(pidfile_path, 'r') as pidfile:
+            pid = int(pidfile.read())
+    except IOError as e:
+        # Ignore errors due to the file not existing. Other errors mean (most
+        # likely) that we're not running as root, there's a filesystem error,
+        # or someone filled our PID file with garbage, so just let those be
+        # raised.
+        if e.errno != errno.ENOENT:
+            raise
+    else:
+        # Is the PID still running?
+        if not os.path.isdir("/proc/%s" % pid):
+            log.info("Removing stale PID file: %s no longer running", pid)
+            os.unlink(pidfile_path)
+        # Is the PID anaconda?
+        else:
+            try:
+                with open("/proc/%s/stat" % pid, "r") as pidstat:
+                    # The part we care about is in the start, "PID (name) ..."
+                    procname = pidstat.read().split(' ', 2)[1]
+                    if procname != "(anaconda)":
+                        log.info("Removing stale PID file: PID %s is now %s", pid, procname)
+                        os.unlink(pidfile_path)
+                    # If it is anaconda, let the pidfile creation below fail
+                    # and print an error
+            except IOError as e:
+                # Ignore failures due to the file not existing in case the
+                # process ended while we were trying to read about it. Assume
+                # in this case that the process was another anaconda instance,
+                # and the PID file was cleaned up.
+                # If the process ended between open and read, we'll get ESRCH
+                if e.errno not in (errno.ENOENT, errno.ESRCH):
+                    raise
+
+    # Attempt to create the pidfile
+    try:
+        # Set all of the read/write bits and let umask make it make sense
+        pidfile = os.open(pidfile_path, os.O_WRONLY|os.O_CREAT|os.O_EXCL,
+                stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IWGRP|stat.S_IROTH|stat.S_IWOTH)
+        pidfile_created = True
+        os.write(pidfile, "%s\n" % os.getpid())
+        os.close(pidfile)
+    except OSError as e:
+        # If the failure was anything other than EEXIST during the open call,
+        # just re-raise the exception
+        if e.errno != errno.EEXIST:
+            raise
+
+        log.error("%s already exists, exiting", pidfile_path)
+
+        # If we have a $DISPLAY and zenity is available, we may be running in
+        # a live environment and we can display an error dialog. Otherwise
+        # just print an error.
+        if "DISPLAY" in os.environ and os.access("/usr/bin/zenity", os.X_OK):
+            # The module-level _() calls are ok here because the language may
+            # be set from the live environment in this case, and anaconda's
+            # language setup hasn't happened yet.
+            # pylint: disable=found-_-in-module-class
+            iutil.execWithRedirect("zenity",
+                ["--error", "--title", _("Unable to create PID file"), "--text",
+                    _("Anaconda is unable to create %s because the file" +
+                      " already exists. Anaconda is already running, or a previous instance" +
+                      " of anaconda has crashed.") % pidfile_path])
+        else:
+            print("%s already exists, exiting" % pidfile_path)
+
+        iutil.ipmi_report(constants.IPMI_FAILED)
+        sys.exit(1)
+
     # add our own additional signal handlers
     signal.signal(signal.SIGHUP, startDebugger)
 
-- 
2.1.0



More information about the anaconda-patches mailing list