1#!/usr/bin/env python3
2# $OpenLDAP$
3## This work is part of OpenLDAP Software <http://www.openldap.org/>.
4##
5## Copyright 2020-2021 The OpenLDAP Foundation.
6## All rights reserved.
7##
8## Redistribution and use in source and binary forms, with or without
9## modification, are permitted only as authorized by the OpenLDAP
10## Public License.
11##
12## A copy of this license is available in the file LICENSE in the
13## top-level directory of the distribution or, alternatively, at
14## <http://www.OpenLDAP.org/license.html>.
15"""
16Running slapd under GDB in our testsuite, KILLPIDS would record gdb's PID
17rather than slapd's. When we want the server to shut down, SIGHUP is sent to
18KILLPIDS but GDB cannot handle being signalled directly and the entire thing is
19terminated immediately. There might be tests that rely on slapd being given the
20chance to shut down gracefully, to do this, we need to make sure the signal is
21actually sent to slapd.
22
23This script attempts to address this shortcoming in our test suite, serving as
24the front for gdb/other wrappers, catching SIGHUPs and redirecting them to the
25oldest living grandchild. The way we start up gdb, that process should be
26slapd, our intended target.
27
28This requires the pgrep utility provided by the procps package on Debian
29systems.
30"""
31
32import asyncio
33import os
34import signal
35import sys
36
37
38async def signal_to_grandchild(child):
39    # Get the first child, that should be the one we're after
40    pgrep = await asyncio.create_subprocess_exec(
41            "pgrep", "-o", "--parent", str(child.pid),
42            stdout=asyncio.subprocess.PIPE)
43
44    stdout, _ = await pgrep.communicate()
45    if not stdout:
46        return
47
48    grandchild = [int(pid) for pid in stdout.split()][0]
49
50    os.kill(grandchild, signal.SIGHUP)
51
52
53def sighup_handler(child):
54    asyncio.create_task(signal_to_grandchild(child))
55
56
57async def main(args=None):
58    if args is None:
59        args = sys.argv[1:]
60
61    child = await asyncio.create_subprocess_exec(*args)
62
63    # If we got a SIGHUP before we got the child fully started, there's no
64    # point signalling anyway
65    loop = asyncio.get_running_loop()
66    loop.add_signal_handler(signal.SIGHUP, sighup_handler, child)
67
68    raise SystemExit(await child.wait())
69
70
71if __name__ == '__main__':
72    asyncio.run(main())
73