Files
git-hooks/hooks/daemon.py
Joel Brobecker 2a0e09842e daemon_utest: Stop using testcase.enable_unit_test
For some reason that I was unable to determine, trying to cover
the case failure to fork during the second fork causes an error
when running the testsuite with coverage, such as:

     $ ./coverage.sh -j16 --enable-color tests/LC28-010__daemon_utest

We get a crash during the execution of the at_exit handling when
the coverage framework tries to create a file in the root ("/")
directory, presumably to save the coverage information. I couldn't
figure out why this was happening, nor could I find a fix. Since
the code we're trying is not expected to change much over time,
and the second fork error handling is only about damage control,
I gave up on the idea. Instead, I commented out the corresponding
testing code, so as for it to be available should we try to solve
that mystery again, and I added a "pragma nocover" in run_in_daemon.

This had the side-effect of dropping one call to syslog that was
contributing to full coverage. This commit simply extends the unit
test to include a call to that function as well.

TN: U627-007
Change-Id: Iaacfd174747ff43a921f5d17e3a74900ad96a78e
2021-06-30 07:25:43 -07:00

98 lines
2.7 KiB
Python

"""A module to handle daemonization...
"""
from __future__ import print_function
import os
import sys
from syslog import syslog
def daemonize(output_fd=None):
"""Create a daemon process.
PARAMETERS
output_fd: If not none, a file descriptor where stdout and
stderr should be redirected.
RETURN VALUE
This function returns True for the child (daemon) process,
while it returns False for the parent process.
"""
# Flush the output a first time to make sure the child processes
# do not inherit some buffered output from the parent, causing
# some output generated by the parent to be printed multiple times
# due to both parent and child printing it at flush time.
for f in sys.stdout, sys.stderr:
f.flush()
# Perform the first fork.
try:
pid = os.fork()
if pid > 0:
# In the parent. We can now return.
return False
except OSError as e:
sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
return False
# Decouple ourselves from the parent environment.
os.chdir("/")
os.umask(0)
os.setsid()
# Perform the second fork, to daemonize ourselves.
try:
pid = os.fork()
if pid > 0:
# In the second parent. Exit.
sys.exit(0)
except OSError as e: # pragma: no cover (really hard to cover -- see U627-007)
syslog("git-hooks: fork #2 failed: (%d) %s" % (e.errno, e.strerror))
sys.exit(1)
# Flush the output...
for f in sys.stdout, sys.stderr:
f.flush()
# Perform the input/output redirection.
os.close(0)
os.dup2(os.open("/dev/null", os.O_RDONLY), 0)
if output_fd is None: # pragma: no cover (never true in testsuite mode)
output_fd = os.open("/dev/null", os.O_WRONLY)
os.close(1)
os.dup2(output_fd, 1)
os.close(2)
os.dup2(output_fd, 2)
return True
def run_in_daemon(fun):
"""Run the given callbable in a daemon process.
In GIT_HOOKS_TESTSUITE_MODE, the function's stdout and stderr
is redirected to a pipe and then re-printed on our stdout.
But this is only for testing purposes. In normal mode,
the function's stdout/stderr, as well as stdin are redirected
to /dev/null.
PARAMETERS
fun: A callable.
"""
daemon_pipe = (None, None)
if "GIT_HOOKS_TESTSUITE_MODE" in os.environ:
daemon_pipe = os.pipe()
in_daemon = daemonize(daemon_pipe[1])
if in_daemon:
fun()
sys.exit(0)
else:
if daemon_pipe[0] is not None:
os.close(daemon_pipe[1])
daemon_stdout = os.fdopen(daemon_pipe[0])
print(daemon_stdout.read(), file=sys.stderr, end="")
daemon_stdout.close()