Last active
November 27, 2019 19:24
-
-
Save adamhooper/441e3905c7a28193300f73ac5d7de20e to your computer and use it in GitHub Desktop.
Clone a process with new file descriptors
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import socket | |
from c_clone import libc_clone | |
# Primer on global variables: we set these before clone(), so they're set in | |
# both the spawner process and the child process. Set them to None when they're | |
# no longer needed, to make code easier to read. | |
# | |
# Primer on pipes: `os.pipe()` creates two file descriptors: a "read" and a | |
# "write". Data written to the "write" end can (and must) be read by the "read" | |
# pipe. The "read" end will only reach EOF after the "write" end is closed. | |
# | |
# Primer on file descriptors ("fd"s): an fd is an integer. The kernel lets you | |
# use this fd in system calls. You can duplicate ("dup") an fd to give it a new | |
# integer; then both fds will behave as one. A clone()d process starts with dups | |
# of all the parent's fds. Close _all_ dups of an fd (across all processes) to | |
# close the underlying resource. | |
stdin_r, stdin_w = None, None | |
stdout_r, stdout_w = None, None | |
stderr_r, stderr_w = None, None | |
# Primer on sockets: we use it like a pipe, but it can send file descriptors | |
sock = None | |
def _child_close_socket(): | |
global sock | |
os.close(sock.fileno()) | |
sock = None | |
def _child_reset_fds(): | |
global stdin_r, stdin_w, stdout_r, stdout_w, stderr_r, stderr_w | |
os.close(stdin_w) | |
os.close(stdout_r) | |
os.close(stderr_r) | |
if stdin_r > 0: | |
os.dup2(stdin_r, 0) | |
os.close(stdin_r) | |
if stdout_w > 1: | |
os.dup2(stdout_w, 1) | |
os.close(stdout_w) | |
if stderr_w > 2: | |
os.dup2(stderr_w, 2) | |
os.close(stderr_w) | |
stdin_r, stdin_w = None, None | |
stdout_r, stdout_w = None, None | |
stderr_r, stderr_w = None, None | |
def _spawner_create_fds_before_clone(): | |
global stdin_r, stdin_w, stdout_r, stdout_w, stderr_r, stderr_w | |
stdin_r, stdin_w = os.pipe() | |
stdout_r, stdout_w = os.pipe() | |
stderr_r, stderr_w = os.pipe() | |
def _spawner_clean_fds_after_clone(): | |
global stdin_r, stdin_w, stdout_r, stdout_w, stderr_r, stderr_w | |
stdin_r, stdin_w = None, None | |
stdout_r, stdout_w = None, None | |
stderr_r, stderr_w = None, None | |
def _spawner_send_child_info_to_parent(child_pid): | |
# ... send parent (child_pid, stdin_w, stdout_r, stderr_r) | |
pass | |
def child(): | |
_child_close_socket() | |
_child_reset_fds() | |
print("Hello from the child!") | |
os._exit(0) | |
def spawner(): | |
# ... import Python modules | |
while True: | |
# Wait for parent to signal us | |
_spawner_create_fds_before_clone() | |
child_pid = libc_clone(child) | |
_spawner_send_child_info_to_parent(child_pid) | |
_spawner_clean_fds_after_clone() | |
# ... parent must read() from stdout_r and stderr_r. (We've closed | |
# all other duplicates of those fds.) | |
# | |
# ... parent _must_ call os.waitpid(child_pid, os.WEXITED). |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment