Module rsyscall.linux.futex

#include <linux/futex.h>

Expand source code Browse git
"`#include <linux/futex.h>`"
from __future__ import annotations
import typing as t
from rsyscall._raw import ffi, lib # type: ignore
import enum
from dataclasses import dataclass
from rsyscall.struct import Struct
from rsyscall.handle import Pointer, WrittenPointer

FUTEX_WAITERS: int = lib.FUTEX_WAITERS
FUTEX_TID_MASK: int = lib.FUTEX_TID_MASK

@dataclass
class FutexNode(Struct):
    # this is our bundle of struct robust_list with a futex.  since it's tricky to handle the
    # reference management of taking a reference to just one field in a structure (the futex, in
    # cases where we don't care about the robust list), we always deal in the entire FutexNode
    # structure whenever we talk about futexes. that's a bit of overhead but we barely use futexes,
    # so it's fine.
    next: t.Optional[Pointer[FutexNode]]
    futex: int

    def to_bytes(self) -> bytes:
        struct = ffi.new('struct futex_node*', {
            # technically we're supposed to have a pointer to the first node in the robust list to
            # indicate the end.  but that's tricky to do. so instead let's just use a NULL pointer;
            # the kernel will EFAULT when it hits the end. make sure not to map 0, or we'll
            # break. https://imgflip.com/i/2zwysg
            'list': (ffi.cast('struct robust_list*', int(self.next.near)) if self.next else ffi.NULL,),
            'futex': self.futex,
        })
        return bytes(ffi.buffer(struct))

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

@dataclass
class RobustListHead(Struct):
    first: WrittenPointer[FutexNode]

    def to_bytes(self) -> bytes:
        struct = ffi.new('struct robust_list_head*', {
            'list': (ffi.cast('struct robust_list*', int(self.first.near)),),
            'futex_offset': ffi.offsetof('struct futex_node', 'futex'),
            'list_op_pending': ffi.NULL,
        })
        return bytes(ffi.buffer(struct))

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

#### Classes ####
import rsyscall.far
from rsyscall.handle.pointer import WrittenPointer

class FutexTask(rsyscall.far.Task):
    async def set_robust_list(self, head: WrittenPointer[RobustListHead]) -> None:
        with head.borrow(self):
            await _set_robust_list(self.sysif, head.near, head.size())

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

async def _set_robust_list(sysif: SyscallInterface, head: near.Address, len: int) -> None:
    await sysif.syscall(SYS.set_robust_list, head, len)

Classes

class FutexNode (next: t.Optional[Pointer[FutexNode]], futex: int)

FutexNode(next: 't.Optional[Pointer[FutexNode]]', futex: 'int')

Expand source code Browse git
@dataclass
class FutexNode(Struct):
    # this is our bundle of struct robust_list with a futex.  since it's tricky to handle the
    # reference management of taking a reference to just one field in a structure (the futex, in
    # cases where we don't care about the robust list), we always deal in the entire FutexNode
    # structure whenever we talk about futexes. that's a bit of overhead but we barely use futexes,
    # so it's fine.
    next: t.Optional[Pointer[FutexNode]]
    futex: int

    def to_bytes(self) -> bytes:
        struct = ffi.new('struct futex_node*', {
            # technically we're supposed to have a pointer to the first node in the robust list to
            # indicate the end.  but that's tricky to do. so instead let's just use a NULL pointer;
            # the kernel will EFAULT when it hits the end. make sure not to map 0, or we'll
            # break. https://imgflip.com/i/2zwysg
            'list': (ffi.cast('struct robust_list*', int(self.next.near)) if self.next else ffi.NULL,),
            'futex': self.futex,
        })
        return bytes(ffi.buffer(struct))

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

Ancestors

Class variables

var next : Optional[Pointer[FutexNode]]
var futex : int

Inherited members

class RobustListHead (first: WrittenPointer[FutexNode])

RobustListHead(first: 'WrittenPointer[FutexNode]')

Expand source code Browse git
@dataclass
class RobustListHead(Struct):
    first: WrittenPointer[FutexNode]

    def to_bytes(self) -> bytes:
        struct = ffi.new('struct robust_list_head*', {
            'list': (ffi.cast('struct robust_list*', int(self.first.near)),),
            'futex_offset': ffi.offsetof('struct futex_node', 'futex'),
            'list_op_pending': ffi.NULL,
        })
        return bytes(ffi.buffer(struct))

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

Ancestors

Class variables

var firstWrittenPointer[FutexNode]

Inherited members

class FutexTask (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 FutexTask(rsyscall.far.Task):
    async def set_robust_list(self, head: WrittenPointer[RobustListHead]) -> None:
        with head.borrow(self):
            await _set_robust_list(self.sysif, head.near, head.size())

Ancestors

Subclasses

Class variables

var sysifSyscallInterface
var near_processProcess
var fd_tableFDTable
var address_spaceAddressSpace
var pidnsPidNamespace

Methods

async def set_robust_list(self, head: WrittenPointer[RobustListHead]) ‑> NoneType
Expand source code Browse git
async def set_robust_list(self, head: WrittenPointer[RobustListHead]) -> None:
    with head.borrow(self):
        await _set_robust_list(self.sysif, head.near, head.size())