This patch copies the pipe-based error reporting functionality from
utils.StartDaemon (I gave up for now on tryin to merge the two).
This patch will fix two longstanding bugs:
- if we fork, we lose all error reporting from the child to the original
parent
- if we fork, the original parent exits before the child is ready to
"work" (whatever the work might be)
Both these are fixed once the users of daemon.GenericMain are converted
to the three-state setup, as we'll get error reporting via the pipe and
also not exit until the PrepFn is done.
---
lib/daemon.py | 38 +++++++++++++++++++++++++-------------
lib/utils.py | 25 ++++++++++++++++++++-----
2 files changed, 45 insertions(+), 18 deletions(-)
diff --git a/lib/daemon.py b/lib/daemon.py
index eceeac4..ba5f175 100644
--- a/lib/daemon.py
+++ b/lib/daemon.py
@@ -624,22 +624,34 @@ def GenericMain(daemon_name, optionparser,
if options.fork:
utils.CloseFDs()
- utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
+ wpipe = utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
+ else:
+ wpipe = None
utils.WritePidFile(utils.DaemonPidFileName(daemon_name))
try:
- utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
- debug=options.debug,
- stderr_logging=not options.fork,
- multithreaded=multithreaded,
- program=daemon_name,
- syslog=options.syslog,
- console_logging=console_logging)
- logging.info("%s daemon startup", daemon_name)
- if callable(prepare_fn):
- prep_results = prepare_fn(options, args)
- else:
- prep_results = None
+ try:
+ utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
+ debug=options.debug,
+ stderr_logging=not options.fork,
+ multithreaded=multithreaded,
+ program=daemon_name,
+ syslog=options.syslog,
+ console_logging=console_logging)
+ if callable(prepare_fn):
+ prep_results = prepare_fn(options, args)
+ else:
+ prep_results = None
+ logging.info("%s daemon startup", daemon_name)
+ except Exception, err:
+ if wpipe is not None:
+ os.write(wpipe, str(err))
+ raise
+
+ if wpipe is not None:
+ # we're done with the preparation phase, we close the pipe to
+ # let the parent know it's safe to exit
+ os.close(wpipe)
exec_fn(options, args, prep_results)
finally:
diff --git a/lib/utils.py b/lib/utils.py
index 9e04d98..54588cf 100644
--- a/lib/utils.py
+++ b/lib/utils.py
@@ -348,8 +348,8 @@ def StartDaemon(cmd, env=None, cwd="/", output=None,
output_fd=None,
finally:
_CloseFDNoErr(errpipe_write)
- # Wait for daemon to be started (or an error message to arrive) and
read
- # up to 100 KB as an error message
+ # Wait for daemon to be started (or an error message to
+ # arrive) and read up to 100 KB as an error message
errormsg = RetryOnSignal(os.read, errpipe_read, 100 * 1024)
finally:
_CloseFDNoErr(errpipe_read)
@@ -2146,6 +2146,12 @@ def Daemonize(logfile):
# pylint: disable-msg=W0212
# yes, we really want os._exit
+ # TODO: do another attempt to merge Daemonize and StartDaemon, or at
+ # least abstract the pipe functionality between them
+
+ # Create pipe for sending error messages
+ (rpipe, wpipe) = os.pipe()
+
# this might fail
pid = os.fork()
if (pid == 0): # The first child.
@@ -2154,15 +2160,24 @@ def Daemonize(logfile):
# this might fail
pid = os.fork() # Fork a second child.
if (pid == 0): # The second child.
- pass # nothing special to do in the child
+ _CloseFDNoErr(rpipe)
else:
# exit() or _exit()? See below.
os._exit(0) # Exit parent (the first child) of the second child.
else:
- os._exit(0) # Exit parent of the first child.
+ _CloseFDNoErr(wpipe)
+ # Wait for daemon to be started (or an error message to
+ # arrive) and read up to 100 KB as an error message
+ errormsg = RetryOnSignal(os.read, rpipe, 100 * 1024)
+ if errormsg:
+ sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
+ rcode = 1
+ else:
+ rcode = 0
+ os._exit(rcode) # Exit parent of the first child.
SetupDaemonFDs(logfile, None)
- return 0
+ return wpipe
def DaemonPidFileName(name):
--
1.7.1