async def move_fds(self, fds: t.List[FileDescriptor]) -> t.List[FileDescriptor]: "Move the passed-in file descriptors from self.access_task to self.task" if self.access_task.fd_table == self.task.fd_table: return [fd.move(self.task) for fd in fds] iovec = await self.access_task.ptr( IovecList([await self.access_task.malloc(bytes, 1)])) cmsgs = await self.access_task.ptr( CmsgList([CmsgSCMRights([fd for fd in fds])])) _, [] = await self.access_fd.sendmsg(await self.access_task.ptr( SendMsghdr(None, iovec, cmsgs))) iovec = await self.task.ptr( IovecList([await self.task.malloc(bytes, 1)])) cmsgs = await self.task.ptr( CmsgList([CmsgSCMRights([fd for fd in fds])])) _, [], hdr = await self.fd.recvmsg(await self.task.ptr( RecvMsghdr(None, iovec, cmsgs))) cmsgs_ptr = (await hdr.read()).control if cmsgs_ptr is None: raise Exception("cmsgs field of header is, impossibly, None") [cmsg] = await cmsgs_ptr.read() if not isinstance(cmsg, CmsgSCMRights): raise Exception("expected SCM_RIGHTS cmsg, instead got", cmsg) passed_socks = cmsg for sock in fds: await sock.close() return passed_socks
async def _connect_and_send( self: PersistentProcess, process: Process, syscall_sock: FileDescriptor, data_sock: FileDescriptor, ) -> t.Tuple[FileDescriptor, FileDescriptor]: """Connect to a persistent process's socket, send some file descriptors """ fds = [syscall_sock, data_sock] sock = await process.make_afd(await process.socket(AF.UNIX, SOCK.STREAM|SOCK.NONBLOCK)) sockaddr_un = await SockaddrUn.from_path(process, self.persistent_path) addr = await process.ptr(sockaddr_un) count = await process.ptr(Int32(len(fds))) iovec = await process.ptr(IovecList([await process.malloc(bytes, 1)])) cmsgs = await process.ptr(CmsgList([CmsgSCMRights(fds)])) hdr = await process.ptr(SendMsghdr(None, iovec, cmsgs)) response: Pointer = await process.ptr(StructList(Int32, [Int32(0)]*len(fds))) data = None await sock.connect(addr) _, _ = await sock.write(count) _, [] = await sock.handle.sendmsg(hdr, SendmsgFlags.NONE) while response.size() > 0: valid, response = await sock.read(response) data += valid remote_syscall_sock, remote_data_sock = [self.task.make_fd_handle(near.FileDescriptor(int(i))) for i in ((await data.read()).elems if data else [])] await sock.close() return remote_syscall_sock, remote_data_sock
async def sendmsg_op(sem: RAM) -> WrittenPointer[SendMsghdr]: iovec = await sem.ptr(IovecList([await sem.malloc(bytes, 1)])) cmsgs = await sem.ptr( CmsgList([ CmsgSCMRights( [passed_syscall_sock, passed_data_sock, connection_fd]) ])) return await sem.ptr(SendMsghdr(None, iovec, cmsgs))
async def sendmsg_op( sem: RAM ) -> t.Tuple[WrittenPointer[SockaddrUn], WrittenPointer[Int32], WrittenPointer[SendMsghdr], Pointer[StructList[Int32]]]: addr = await sem.ptr(sockaddr_un) count = await sem.ptr(Int32(len(fds))) iovec = await sem.ptr(IovecList([await sem.malloc(bytes, 1)])) cmsgs = await sem.ptr(CmsgList([CmsgSCMRights(fds)])) hdr = await sem.ptr(SendMsghdr(None, iovec, cmsgs)) response_buf = await sem.ptr(StructList(Int32, [Int32(0)] * len(fds))) return addr, count, hdr, response_buf
async def recvmsg_op(sem: RAM) -> WrittenPointer[RecvMsghdr]: iovec = await sem.ptr(IovecList([await sem.malloc(bytes, 1)])) cmsgs = await sem.ptr(CmsgList([CmsgSCMRights([fd for fd in fds])])) return await sem.ptr(RecvMsghdr(None, iovec, cmsgs))
async def stdin_bootstrap( parent: Process, bootstrap_command: Command, ) -> t.Tuple[AsyncChildPid, Process]: """Create a process from running an arbitrary command which must run rsyscall-stdin-bootstrap bootstrap_command can be any arbitrary command, but it must eventually exec rsyscall-stdin-bootstrap, and pass down stdin when it does. We'll clone and exec bootstrap_command, passing down a socketpair for stdin, and try to bootstrap over the other end of the socketpair. Once rsyscall-stdin-bootstrap starts, it will respond to our bootstrap and we'll create a new process. """ #### clone and exec into the bootstrap command # create the socketpair that will be used as stdin stdin_pair = await (await parent.task.socketpair( AF.UNIX, SOCK.STREAM, 0, await parent.task.malloc(Socketpair))).read() parent_sock = stdin_pair.first child = await parent.fork() # set up stdin with socketpair await child.task.inherit_fd(stdin_pair.second).dup2(child.stdin) await stdin_pair.second.close() # exec child_pid = await child.exec(bootstrap_command) #### set up all the fds we'll want to pass over # the basic connections [(access_syscall_sock, passed_syscall_sock), (access_data_sock, passed_data_sock) ] = await parent.open_async_channels(2) # send the fds to the new process connection_fd, make_connection = await parent.connection.prep_fd_transfer() iovec = await parent.ptr(IovecList([await parent.malloc(bytes, 1)])) cmsgs = await parent.ptr( CmsgList([ CmsgSCMRights( [passed_syscall_sock, passed_data_sock, connection_fd]) ])) _, [] = await parent_sock.sendmsg( await parent.ptr(SendMsghdr(None, iovec, cmsgs)), SendmsgFlags.NONE) # close our reference to fds that only the new process needs await passed_syscall_sock.close() await passed_data_sock.close() # close the socketpair await parent_sock.close() #### read describe to get all the information we need from the new process describe_buf = AsyncReadBuffer(access_data_sock) describe_struct = await describe_buf.read_cffi( 'struct rsyscall_stdin_bootstrap') environ = await describe_buf.read_envp(describe_struct.envp_count) #### build the new task pid = describe_struct.pid fd_table = handle.FDTable(pid) address_space = far.AddressSpace(pid) # we assume pid namespace is shared # TODO include namespace inode numbers numbers in describe # note: if we start dealing with namespace numbers then we need to # have a Kernel namespace which tells us which kernel we get those # numbers from. # oh hey we can conveniently dump the inode numbers with getdents! pidns = parent.task.pidns # we assume mount namespace is not shared (can't hurt) mountns = far.MountNamespace(pid) pid = near.Pid(pid) base_task = Task(pid, fd_table, address_space, pidns, mountns) remote_syscall_fd = base_task.make_fd_handle( near.FileDescriptor(describe_struct.syscall_fd)) base_task.sysif = SyscallConnection( logger.getChild(str(pid)), access_syscall_sock, remote_syscall_fd, ) base_task.allocator = await memory.AllocatorClient.make_allocator(base_task ) # we assume our SignalMask is zero'd before being started, so we don't inherit it # TODO I think I can maybe elide creating this epollcenter and instead inherit it or share it, maybe? epoller = await Epoller.make_root(base_task) child_monitor = await ChildPidMonitor.make(base_task, epoller) connection = make_connection( base_task, base_task.make_fd_handle( near.FileDescriptor(describe_struct.connecting_fd))) new_parent = Process( task=base_task, connection=connection, loader=NativeLoader.make_from_symbols(base_task, describe_struct.symbols), epoller=epoller, child_monitor=child_monitor, environ=Environment.make_from_environ(base_task, environ), stdin=base_task.make_fd_handle(near.FileDescriptor(0)), stdout=base_task.make_fd_handle(near.FileDescriptor(1)), stderr=base_task.make_fd_handle(near.FileDescriptor(2)), ) return child_pid, new_parent
async def _setup_stub( process: Process, bootstrap_sock: FileDescriptor, ) -> t.Tuple[t.List[str], Process]: "Setup a stub process" [(access_syscall_sock, passed_syscall_sock), (access_data_sock, passed_data_sock) ] = await process.open_async_channels(2) # memfd for setting up the futex futex_memfd = await process.task.memfd_create(await process.task.ptr( Path("child_robust_futex_list"))) # send the fds to the new process connection_fd, make_connection = await process.connection.prep_fd_transfer( ) iovec = await process.ptr(IovecList([await process.malloc(bytes, 1)])) cmsgs = await process.ptr( CmsgList([ CmsgSCMRights([ passed_syscall_sock, passed_data_sock, futex_memfd, connection_fd ]) ])) _, [] = await bootstrap_sock.sendmsg( await process.ptr(SendMsghdr(None, iovec, cmsgs)), SendmsgFlags.NONE) # close our reference to fds that only the new process needs await passed_syscall_sock.invalidate() await passed_data_sock.invalidate() # close the socketpair await bootstrap_sock.invalidate() #### read describe to get all the information we need from the new process describe_buf = AsyncReadBuffer(access_data_sock) describe_struct = await describe_buf.read_cffi('struct rsyscall_unix_stub') argv_raw = await describe_buf.read_length_prefixed_array( describe_struct.argc) argv = [os.fsdecode(arg) for arg in argv_raw] environ = await describe_buf.read_envp(describe_struct.envp_count) #### build the new task pid = describe_struct.pid fd_table = handle.FDTable(pid) address_space = far.AddressSpace(pid) # we assume pid namespace is shared pidns = process.task.pidns # we assume mount namespace is not shared (won't hurt) mountns = far.MountNamespace(pid) pid = near.Pid(pid) # we assume net namespace is shared - that's dubious... # we should make it possible to control the namespace sharing more, hmm. # TODO maybe the describe should contain the net namespace number? and we can store our own as well? # then we can automatically do it right base_task = Task(pid, fd_table, address_space, pidns, mountns) remote_syscall_fd = base_task.make_fd_handle( near.FileDescriptor(describe_struct.syscall_fd)) base_task.sysif = SyscallConnection( logger.getChild(str(pid)), access_syscall_sock, remote_syscall_fd, ) base_task.allocator = await memory.AllocatorClient.make_allocator(base_task ) base_task.sigmask = Sigset( {SIG(bit) for bit in rsyscall.struct.bits(describe_struct.sigmask)}) # TODO I think I can maybe elide creating this epollcenter and instead inherit it or share it, maybe? # I guess I need to write out the set too in describe epoller = await Epoller.make_root(base_task) child_monitor = await ChildPidMonitor.make(base_task, epoller) connection = make_connection( base_task, base_task.make_fd_handle( near.FileDescriptor(describe_struct.connecting_fd))) new_process = Process( task=base_task, connection=connection, loader=NativeLoader.make_from_symbols(base_task, describe_struct.symbols), epoller=epoller, child_monitor=child_monitor, environ=Environment.make_from_environ(base_task, environ), stdin=base_task.make_fd_handle(near.FileDescriptor(0)), stdout=base_task.make_fd_handle(near.FileDescriptor(1)), stderr=base_task.make_fd_handle(near.FileDescriptor(2)), ) #### TODO set up futex I guess remote_futex_memfd = near.FileDescriptor(describe_struct.futex_memfd) return argv, new_process