Module rsyscall.unistd.pipe

Expand source code Browse git
from __future__ import annotations
from rsyscall._raw import ffi, lib # type: ignore
from dataclasses import dataclass
from rsyscall.struct import FixedSize, Serializer
import rsyscall.near.types as near
from rsyscall.handle.fd import FileDescriptorTask
import typing as t
if t.TYPE_CHECKING:
    from rsyscall.handle import FileDescriptor

T_pipe = t.TypeVar('T_pipe', bound='Pipe')
@dataclass
class Pipe(FixedSize):
    "A pair of file descriptors, as written by pipe."
    read: FileDescriptor
    write: FileDescriptor

    def __getitem__(self, idx: int) -> FileDescriptor:
        if idx == 0:
            return self.read
        elif idx == 1:
            return self.write
        else:
            raise IndexError("only index 0 or 1 are valid for Pipe:", idx)

    def __iter__(self) -> t.Iterable[FileDescriptor]:
        return iter([self.read, self.write])

    @classmethod
    def sizeof(cls) -> int:
        return ffi.sizeof('struct fdpair')

    @classmethod
    def get_serializer(cls: t.Type[T_pipe], task: FileDescriptorTask[FileDescriptor]) -> Serializer[T_pipe]:
        return PipeSerializer(cls, task)

@dataclass
class PipeSerializer(Serializer[T_pipe]):
    cls: t.Type[T_pipe]
    task: FileDescriptorTask[FileDescriptor]

    def to_bytes(self, pair: T_pipe) -> bytes:
        struct = ffi.new('struct fdpair*', (pair.read, pair.write))
        return bytes(ffi.buffer(struct))

    def from_bytes(self, data: bytes) -> T_pipe:
        struct = ffi.cast('struct fdpair const*', ffi.from_buffer(data))
        def make(n: int) -> FileDescriptor:
            return self.task.make_fd_handle(near.FileDescriptor(int(n)))
        return self.cls(make(struct.first), make(struct.second))

#### Classes ####
from rsyscall.fcntl import O
from rsyscall.handle.pointer import Pointer, LinearPointer

class PipeTask(FileDescriptorTask):
    async def pipe(self, buf: Pointer[Pipe], flags: O=O.NONE) -> LinearPointer[Pipe]:
        """create pipe

        manpage: pipe2(2)
        """
        # TODO we should force the serializer for the pipe to be using this task...
        # otherwise it could get deserialized by a task with which we share memory,
        # but not share file descriptor tables.
        # Maybe we could create the Serializer right here, and discard
        # the passed-in one? That wouldn't allow a different task in
        # the same fd table to receive the handles though.
        with buf.borrow(self):
            await _pipe(self.sysif, buf.near, flags|O.CLOEXEC)
            return buf._linearize()

#### Raw syscalls ####
from rsyscall.near.sysif import SyscallInterface
from rsyscall.sys.syscall import SYS

async def _pipe(sysif: SyscallInterface, pipefd: near.Address, flags: O) -> None:
    await sysif.syscall(SYS.pipe2, pipefd, flags)

Classes

class Pipe (read: FileDescriptor, write: FileDescriptor)

A pair of file descriptors, as written by pipe.

Expand source code Browse git
@dataclass
class Pipe(FixedSize):
    "A pair of file descriptors, as written by pipe."
    read: FileDescriptor
    write: FileDescriptor

    def __getitem__(self, idx: int) -> FileDescriptor:
        if idx == 0:
            return self.read
        elif idx == 1:
            return self.write
        else:
            raise IndexError("only index 0 or 1 are valid for Pipe:", idx)

    def __iter__(self) -> t.Iterable[FileDescriptor]:
        return iter([self.read, self.write])

    @classmethod
    def sizeof(cls) -> int:
        return ffi.sizeof('struct fdpair')

    @classmethod
    def get_serializer(cls: t.Type[T_pipe], task: FileDescriptorTask[FileDescriptor]) -> Serializer[T_pipe]:
        return PipeSerializer(cls, task)

Ancestors

Class variables

var read : FileDescriptor
var write : FileDescriptor

Inherited members

class PipeSerializer (cls: t.Type[T_pipe], task: FileDescriptorTask[FileDescriptor])

PipeSerializer(cls: 't.Type[T_pipe]', task: 'FileDescriptorTask[FileDescriptor]')

Expand source code Browse git
@dataclass
class PipeSerializer(Serializer[T_pipe]):
    cls: t.Type[T_pipe]
    task: FileDescriptorTask[FileDescriptor]

    def to_bytes(self, pair: T_pipe) -> bytes:
        struct = ffi.new('struct fdpair*', (pair.read, pair.write))
        return bytes(ffi.buffer(struct))

    def from_bytes(self, data: bytes) -> T_pipe:
        struct = ffi.cast('struct fdpair const*', ffi.from_buffer(data))
        def make(n: int) -> FileDescriptor:
            return self.task.make_fd_handle(near.FileDescriptor(int(n)))
        return self.cls(make(struct.first), make(struct.second))

Ancestors

Class variables

var cls : t.Type[T_pipe]
var task : FileDescriptorTask[FileDescriptor]

Inherited members

class PipeTask (sysif: SyscallInterface, near_process: Process, fd_table: FDTable, address_space: AddressSpace, pidns: PidNamespace)

A wrapper around SyscallInterface which tracks the namespaces of the underlying process

Note that this is a base class for the more fully featured Task.

We store namespace objects to represent the namespaces that we believe that underlying processes is in. Since we have complete control over the process, we can make sure this belief is accurate, by updating our stored namespaces when the process changes namespace. That isn't done here; it's done in handle.Task.

Currently, we store only one PidNamespace. But each process actually has two pid namespaces:

  • the process's own pid namespace, which determines the pids returned from getpid, clone, and other syscalls.
  • the pid namespace that new children will be in.

The two pid namespaces only differ if we call unshare(CLONE.NEWPID). Currently we don't do that because unshare(CLONE.NEWPID) makes monitoring children more complex, since they can be deleted without leaving a zombie at any time if the pid namespace shuts down. But if we did call unshare(CLONE.NEWPID), we'd need to handle this right.

In the analogy to near and far pointers, this is like a segment register, if a segment register was write-only. Then we'd need to maintain the knowledge of what the segment register was set to, outside the segment register itself. That's what we do here.

There actually were systems where segment registers were, if not quite write-only, at least expensive to set and expensive to read. For example, x86_64 - the FS and GS segment registers can only be set via syscall. If you wanted to use segmentation on such systems, you'd probably have a structure much like this one.

Expand source code Browse git
class PipeTask(FileDescriptorTask):
    async def pipe(self, buf: Pointer[Pipe], flags: O=O.NONE) -> LinearPointer[Pipe]:
        """create pipe

        manpage: pipe2(2)
        """
        # TODO we should force the serializer for the pipe to be using this task...
        # otherwise it could get deserialized by a task with which we share memory,
        # but not share file descriptor tables.
        # Maybe we could create the Serializer right here, and discard
        # the passed-in one? That wouldn't allow a different task in
        # the same fd table to receive the handles though.
        with buf.borrow(self):
            await _pipe(self.sysif, buf.near, flags|O.CLOEXEC)
            return buf._linearize()

Ancestors

Subclasses

Class variables

var sysifSyscallInterface
var near_processProcess
var fd_tableFDTable
var address_spaceAddressSpace
var pidnsPidNamespace

Methods

async def pipe(self, buf: Pointer[Pipe], flags: O = O.NONE) ‑> LinearPointer[Pipe]

create pipe

manpage: pipe2(2)

Expand source code Browse git
async def pipe(self, buf: Pointer[Pipe], flags: O=O.NONE) -> LinearPointer[Pipe]:
    """create pipe

    manpage: pipe2(2)
    """
    # TODO we should force the serializer for the pipe to be using this task...
    # otherwise it could get deserialized by a task with which we share memory,
    # but not share file descriptor tables.
    # Maybe we could create the Serializer right here, and discard
    # the passed-in one? That wouldn't allow a different task in
    # the same fd table to receive the handles though.
    with buf.borrow(self):
        await _pipe(self.sysif, buf.near, flags|O.CLOEXEC)
        return buf._linearize()

Inherited members