#!/usr/bin/env python3
import sys
import os
import os.path
import ctypes
import fcntl
import select
import errno
libc = ctypes.CDLL(None, use_errno=True)
CLONE_NEWPID = 0x20000000
def nonblock_cloexec(fd):
fcntl.fcntl(
fd,
fcntl.F_SETFD,
fcntl.fcntl(fd, fcntl.F_GETFD) | os.O_NONBLOCK | fcntl.FD_CLOEXEC,
)
def exit_status(status):
sig = status & 0xff
ret = status >> 8
if sig:
raise SystemExit(128 + sig)
if ret >= 128:
raise SystemExit(128)
raise SystemExit(ret)
def main(argv):
(parent_rfd, parent_wfd) = os.pipe()
nonblock_cloexec(parent_rfd)
nonblock_cloexec(parent_wfd)
if libc.unshare(CLONE_NEWPID) != 0:
raise OSError(ctypes.get_errno())
fork_pid = os.fork()
if fork_pid == 0:
# child
assert os.getpid() == 1
os.close(parent_wfd)
fork2_pid = os.fork()
if fork2_pid == 0:
# child
if argv[1][0] == '/':
os.execv(argv[1], argv[1:])
for d in os.environ['PATH'].split(':'):
try:
os.execv(os.path.join(d, argv[1]), argv[1:])
except FileNotFoundError:
continue
raise SystemExit(127)
else:
# parent
rlist, wlist, elist = (parent_rfd,), (), ()
while True:
(pid, status) = os.waitpid(0, os.WNOHANG)
if pid == fork2_pid:
exit_status(status)
try:
r, w, x = select.select(rlist, wlist, elist, 1.0)
except select.error as e:
code, msg = e.args
# We might get interrupted by SIGCHLD here
if code != errno.EINTR:
raise
else:
# parent
os.close(parent_rfd)
(pid, status) = os.waitpid(fork_pid, 0)
exit_status(status)
if __name__ == '__main__':
main(sys.argv)