File: //proc/16903/cwd/sbin/wafd_imunify_daemon.bin
#!/opt/imunify360/venv/bin/python3
from contextlib import suppress
import gc
from pathlib import Path
import signal
import subprocess
import sys
import time
import psutil
from wafd_i360.internals import getLogger, logger_init
MAX_FAILURES_PER_HOUR = 10
TRANSIENT_SIGLIST =(signal.SIGABRT,
signal.SIGCONT,
signal.SIGHUP,
signal.SIGINT,
signal.SIGPIPE,
signal.SIGQUIT,
signal.SIGUSR1,
signal.SIGUSR2,
signal.SIGTERM)
PIDFILE = Path('/var/run/wafd_imunify_daemon-watchdog.pid')
logger = getLogger()
def wire_signals(reroute_to: subprocess.Popen=None, undo=False):
def child_proc_send(sig, _):
reroute_to.transient_signal = sig
try:
reroute_to.send_signal(sig)
except ProcessLookupError:
logger.exception("child process died unexpectedly")
for sig in TRANSIENT_SIGLIST:
signal.signal(sig, signal.SIG_DFL if undo else child_proc_send)
def kill_orphaned_child(wafd_imunify_daemon):
""" if any. """
try:
pid = int(PIDFILE.read_text())
except (FileNotFoundError, ValueError):
return
with suppress(psutil.NoSuchProcess):
proc = psutil.Process(pid)
if proc.name() != Path(wafd_imunify_daemon).name:
return
logger.error("orphaned child process: %s", proc)
proc.terminate()
time.sleep(2)
proc.kill()
def daemon_being_watched() -> bool:
wafd_imunify_daemon = '%s.bin' % sys.argv[0]
kill_orphaned_child(wafd_imunify_daemon)
child_proc = subprocess.Popen([wafd_imunify_daemon] + sys.argv[1:])
child_proc.transient_signal = None
PIDFILE.write_text('%s\n' % child_proc.pid)
logger.info("%s has started.", wafd_imunify_daemon)
wire_signals(reroute_to=child_proc)
gc.collect() # to minimize memory footprint while in idle state
try:
rc = child_proc.wait()
except ProcessLookupError:
logger.exception("child process died unexpectedly")
return False
finally:
wire_signals(undo=True)
if child_proc.transient_signal is None:
if rc < 0:
logger.error("%s died unexpectedly (killed by %d).",
wafd_imunify_daemon,
rc)
else:
logger.error("%s stopped unexpectedly (exit code %d).",
wafd_imunify_daemon,
rc)
return False
logger.info("%s exited with code %d (stopped by signal %d).",
wafd_imunify_daemon,
rc,
child_proc.transient_signal)
return True
def daemon_being_watched_loop() -> bool:
failures = []
while True:
ok_stopped = daemon_being_watched()
if ok_stopped:
return True
now = time.time()
failures.append(now)
if (sum(1 for timestamp in failures
if timestamp >= now - 60*60) >=
MAX_FAILURES_PER_HOUR):
return False
if __name__ == '__main__':
try:
logger_init()
ok_stopped = daemon_being_watched_loop()
if not ok_stopped:
logger.error("wafd trouble "
"after %d wafd_imunify_daemon failures.",
MAX_FAILURES_PER_HOUR)
except Exception as e:
logger.exception("unexpected error: %s", e)
sys.exit(1)