pidns_run.py (2149B)
1 #!/usr/bin/env python3 2 import sys 3 import os 4 import os.path 5 import ctypes 6 import fcntl 7 import select 8 import errno 9 10 libc = ctypes.CDLL(None, use_errno=True) 11 CLONE_NEWPID = 0x20000000 12 13 14 def nonblock_cloexec(fd): 15 fcntl.fcntl( 16 fd, 17 fcntl.F_SETFD, 18 fcntl.fcntl(fd, fcntl.F_GETFD) | os.O_NONBLOCK | fcntl.FD_CLOEXEC, 19 ) 20 21 22 def exit_status(status): 23 sig = status & 0xff 24 ret = status >> 8 25 if sig: 26 raise SystemExit(128 + sig) 27 if ret >= 128: 28 raise SystemExit(128) 29 raise SystemExit(ret) 30 31 32 def main(argv): 33 (parent_rfd, parent_wfd) = os.pipe() 34 nonblock_cloexec(parent_rfd) 35 nonblock_cloexec(parent_wfd) 36 if libc.unshare(CLONE_NEWPID) != 0: 37 raise OSError(ctypes.get_errno()) 38 fork_pid = os.fork() 39 if fork_pid == 0: 40 # child 41 assert os.getpid() == 1 42 os.close(parent_wfd) 43 fork2_pid = os.fork() 44 if fork2_pid == 0: 45 # child 46 if argv[1][0] == '/': 47 os.execv(argv[1], argv[1:]) 48 for d in os.environ['PATH'].split(':'): 49 try: 50 os.execv(os.path.join(d, argv[1]), argv[1:]) 51 except FileNotFoundError: 52 continue 53 raise SystemExit(127) 54 else: 55 # parent 56 rlist, wlist, elist = (parent_rfd,), (), () 57 while True: 58 (pid, status) = os.waitpid(0, os.WNOHANG) 59 if pid == fork2_pid: 60 exit_status(status) 61 try: 62 r, w, x = select.select(rlist, wlist, elist, 1.0) 63 except select.error as e: 64 code, msg = e.args 65 # We might get interrupted by SIGCHLD here 66 if code != errno.EINTR: 67 raise 68 if r: 69 sys.stderr.write('pidns_run: parent died, terminating\n') 70 raise SystemExit(111) 71 72 else: 73 # parent 74 os.close(parent_rfd) 75 (pid, status) = os.waitpid(fork_pid, 0) 76 exit_status(status) 77 78 79 if __name__ == '__main__': 80 main(sys.argv)