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