#!/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)