import os
import select
import signal
import sys
import time
import pty
binary, cfg, out, pidf = sys.argv[1:5]
max_seconds = float(sys.argv[5]) if len(sys.argv) > 5 else 600.0
if not os.path.isabs(binary) or not os.access(binary, os.X_OK):
raise SystemExit(f"binary must be an absolute executable path: {binary}")
ctl = pidf + ".ctl"
try:
os.remove(ctl)
except FileNotFoundError:
pass
pid, fd = pty.fork()
if pid == 0:
os.execv(binary, [binary, "start", "--config", cfg])
with open(pidf, "w") as f:
f.write(str(pid))
end = time.time() + max_seconds
sent = False
status = None
try:
with open(out, "wb") as f:
while time.time() < end:
ready, _, _ = select.select([fd], [], [], 0.3)
if ready:
try:
data = os.read(fd, 4096)
except OSError:
break
if not data:
break
f.write(data)
f.flush()
if not sent and os.path.exists(ctl):
os.write(fd, b"\x03")
sent = True
done, child_status = os.waitpid(pid, os.WNOHANG)
if done:
status = child_status
break
finally:
if status is None:
try:
os.write(fd, b"\x03")
except OSError:
pass
deadline = time.time() + 10
while time.time() < deadline:
done, child_status = os.waitpid(pid, os.WNOHANG)
if done:
status = child_status
break
time.sleep(0.1)
if status is None:
os.kill(pid, signal.SIGTERM)
deadline = time.time() + 5
while time.time() < deadline:
done, child_status = os.waitpid(pid, os.WNOHANG)
if done:
status = child_status
break
time.sleep(0.1)
if status is None:
os.kill(pid, signal.SIGKILL)
_, status = os.waitpid(pid, 0)
if os.WIFEXITED(status):
raise SystemExit(os.WEXITSTATUS(status))
raise SystemExit(128 + os.WTERMSIG(status))