daemon: Integrate checking for an existing pidfile into daemonize_start().
[sliver-openvswitch.git] / python / ovs / daemon.py
index eaaaa51..cfb4178 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2010 Nicira Networks
+# Copyright (c) 2010, 2011 Nicira Networks
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -52,6 +52,8 @@ _monitor = False
 # File descriptor used by daemonize_start() and daemonize_complete().
 _daemonize_fd = None
 
+RESTART_EXIT_CODE = 5
+
 def make_pidfile_name(name):
     """Returns the file name that would be used for a pidfile if 'name' were
     provided to set_pidfile()."""
@@ -87,9 +89,9 @@ def is_chdir_enabled():
     return _chdir
 
 def ignore_existing_pidfile():
-    """Normally, die_if_already_running() will terminate the program with a
-    message if a locked pidfile already exists.  If this function is called,
-    die_if_already_running() will merely log a warning."""
+    """Normally, daemonize() or daemonize_start() will terminate the program
+    with a message if a locked pidfile already exists.  If this function is
+    called, an existing pidfile will be replaced, with a warning."""
     global _overwrite_pidfile
     _overwrite_pidfile = True
 
@@ -109,37 +111,17 @@ def set_monitor():
     global _monitor
     _monitor = True
 
-def _already_running():
-    """If a pidfile has been configured and that pidfile already exists and is
-    locked by a running process, returns True.  Otherwise, returns False."""
-    if _pidfile is not None:
-        try:
-            file = open(_pidfile, "r+")
-            try:
-                try:
-                    fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
-                except IOError, e:
-                    if e.errno in [errno.EACCES, errno.EAGAIN]:
-                        return True
-                    logging.error("error locking %s (%s)"
-                                  % (_pidfile, os.strerror(e.errno)))
-                    return False
-            finally:
-                # This releases the lock, which we don't really want.
-                file.close()
-        except IOError, e:
-            if e.errno == errno.ENOENT:
-                return False
-            logging.error("error opening %s (%s)"
-                          % (_pidfile, os.strerror(e.errno)))
-    return False
-
-def die_if_already_running():
+def _die_if_already_running():
     """If a locked pidfile exists, issue a warning message and, unless
     ignore_existing_pidfile() has been called, terminate the program."""
-    if _already_running():
+    if _pidfile is None:
+        return
+    pid = read_pidfile_if_exists(_pidfile)
+    if pid > 0:
         if not _overwrite_pidfile:
-            sys.stderr.write("%s: already running\n" % get_pidfile())
+            msg = "%s: already running as pid %d" % (_pidfile, pid)
+            logging.error("%s, aborting" % msg)
+            sys.stderr.write("%s\n" % msg)
             sys.exit(1)
         else:
             logging.warn("%s: %s already running"
@@ -231,10 +213,15 @@ def _fork_and_wait_for_startup():
         # Running in parent process.
         os.close(wfd)
         ovs.fatal_signal.fork()
-        try:
-            s = os.read(rfd, 1)
-        except OSError, e:
-            s = ""
+        while True:
+            try:
+                s = os.read(rfd, 1)
+                error = 0
+            except OSError, e:
+                s = ""
+                error = e.errno
+            if error != errno.EINTR:
+                break
         if len(s) != 1:
             retval, status = _waitpid(pid, 0)
             if (retval == pid and
@@ -266,6 +253,11 @@ def _fork_notify_startup(fd):
         os.close(fd)
 
 def _should_restart(status):
+    global RESTART_EXIT_CODE
+
+    if os.WIFEXITED(status) and os.WEXITSTATUS(status) == RESTART_EXIT_CODE:
+        return True
+
     if os.WIFSIGNALED(status):
         for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE", "SIGILL",
                         "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):
@@ -351,6 +343,7 @@ def daemonize_start():
             _monitor_daemon(daemon_pid)
         # Running in daemon process
     
+    _die_if_already_running()
     _make_pidfile()
 
 def daemonize_complete():
@@ -373,9 +366,7 @@ Daemon options:
    --overwrite-pidfile     with --pidfile, start even if already running
 """ % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))
 
-def read_pidfile(pidfile):
-    """Opens and reads a PID from 'pidfile'.  Returns the nonnegative PID if
-    successful, otherwise a negative errno value."""
+def __read_pidfile(pidfile, must_exist):
     if _pidfile_dev is not None:
         try:
             s = os.stat(pidfile)
@@ -392,6 +383,8 @@ def read_pidfile(pidfile):
     try:
         file = open(pidfile, "r")
     except IOError, e:
+        if e.errno == errno.ENOENT and not must_exist:
+            return 0
         logging.warning("%s: open: %s" % (pidfile, os.strerror(e.errno)))
         return -e.errno
 
@@ -428,6 +421,16 @@ def read_pidfile(pidfile):
         except IOError:
             pass
 
+def read_pidfile(pidfile):
+    """Opens and reads a PID from 'pidfile'.  Returns the positive PID if
+    successful, otherwise a negative errno value."""
+    return __read_pidfile(pidfile, True)
+
+def read_pidfile_if_exists(pidfile):
+    """Opens and reads a PID from 'pidfile'.  Returns 0 if 'pidfile' does not
+    exist, the positive PID if successful, otherwise a negative errno value."""
+    return __read_pidfile(pidfile, False)
+
 # XXX Python's getopt does not support options with optional arguments, so we
 # have to separate --pidfile (with no argument) from --pidfile-name (with an
 # argument).  Need to write our own getopt I guess.