Skip to content

Encapsulation

Tsukini edited this page Oct 4, 2026 · 1 revision

Note

Section _Encapsulation (group _Tools) · Namespace utils::encapsulation · Headers utils/encapsulation/

RAII wrappers around the POSIX tools: the fds are closed by the destructors, every error is an exception with the errno message as info.

Table of Contents

Pipe & Dup

Pipe Description
Pipe(read = -1, write = -1) / Pipe(fds) Wrap existing fds (or none)
trigger() Create the pipe (Pipe error if already open)
getRead(), getWrite(), getFds() fds (-1 = closed)
closeRead(), closeWrite(), close() Close one side / both
clear() Forget the fds without closing them
Dup Description
Dup(origin = -1, clone = -1) origin will be duplicated on clone
trigger() dup(origin) when clone == -1, else dup2(origin, clone) (Dup error when origin is closed)
getOrigin(), getClone(), setOrigin(fd), setClone(fd) Access
closeOrigin(), closeClone(), close(), clear() Close / forget

Both are movable, not copyable.

Process

Method Description
pipe(read, write) / pipe(Pipe&) / pipe(std::vector<Pipe>&) Pipes created (trigger) by the next spawn
dup(origin, clone = -1) / dup(Dup&) / dup(std::vector<Dup>&) Dups applied in the child (ex: redirect its stdout)
spawn() fork: returns 0 in the child, the pid in the parent
spawn(path, args) fork + execvp(path, args) in the child, returns the pid (exit code 127 when the exec or a dup fails)
replace(path, args) execvp in the current process (never returns: exec or _exit(127))
wait() waitpid, returns a Status{unknown, exited, code, sig}
kill() SIGKILL then wait
is() Is the child alive
isChild(), isParent(), getPid() State
getPipes(), getDups(), clear() Access / close the pipes and dups

Note

The destructor of the parent kills a running child. The dups are checked before the fork: an invalid one throws Dup in the caller.

#define _Encapsulation
#include <utils/utils.hpp>

utils::encapsulation::Pipe pipe;
pipe.trigger();
utils::encapsulation::Process process;
process.dup(pipe.getWrite(), STDOUT_FILENO); // child: stdout -> pipe
(void)process.spawn("echo", {"hello"});
pipe.closeWrite();
// read pipe.getRead() -> "hello\n"
utils::encapsulation::Status status = process.wait(); // status.exited, status.code == 0

Poll

Wrapper of epoll.

Method Description
Poll(flags = EPOLL_CLOEXEC) / init(flags) Create the epoll
link(fd, events, data = nullptr) Watch a fd (EPOLLIN...)
edit(fd, events, data) Change the watched events
unlink(fd) Stop watching (ignored for a closed fd)
wait(delay = -1, limits = 0) Wait for events (delay in ms, -1 = forever), returns the epoll_events
size(), contains(fd), getFd(), close() State

SharedObject

Load a shared library (.so) and its functions.

Method Description
SharedObject(path) dlopen (Dlopen error)
loadFunction<T>(name) dlsym, cast to the function pointer type T (Dlsym error)
isLoaded(), path(), get() State, path, raw handle
using StrlenFn = std::size_t (*)(const char*);
utils::encapsulation::SharedObject libc("libc.so.6");
StrlenFn fn = libc.loadFunction<StrlenFn>("strlen");

SharedMemory

Message passing between processes through a POSIX shared memory divided in slots (queue slots of size bytes). Each instance has an ownership (default: its pid) used to address the messages; a reader thread copies the messages for this instance into a local storage.

Method Description
SharedMemory(ownership = getpid()) Instance with its ownership
init<true, policy>(name, size, queue) Create the shared memory (policy: slot layout, see below)
init(name) Open an existing one (the size and queue are read from it)
send(bytes, target, lastSending, lastTransmission, failsafe) Send a message, returns its Id (failsafe = true waits for a free slot instead of OutOfMemory)
send(bytes, id, target, ...) Answer on an existing Id (conversation)
readable(filter = All) / readable(id) Something to read
read(filter = All) std::optional<std::unordered_map<Id, std::vector<std::vector<std::byte>>>>, removed from the storage
read(id) The messages of one Id
join(last = false) / join(id, last) Wait for a message (or for a last one), returns when the memory is closed
close() Stop the reader thread, unmap (also done by the destructor)
ownership() / ownership(pid) Get / set the ownership
Type Description
Target(limit, ownership) To one reader (ownership), read limit times
Target(limit) Global: to every reader, limit readers (0 = every connected reader except the sender)
Id{id, ownership} Conversation id (0 = no conversation)
ReadFilter All, ZeroOnly (id 0), NonZeroOnly, LastOnly (messages sent with lastSending / lastTransmission)
LayoutPolicy Compact (1 writer), CompactSemiAligned, Interleaved, InterleavedAligned (many writers at the same time, one cache line per slot)
// process A
utils::encapsulation::SharedMemory server(pid_t{1001});
server.init<true>("/my-shm", 64, 4);                        // 4 slots of 64 bytes
utils::encapsulation::shm::Id id = server.send(bytes, utils::encapsulation::shm::Target(1, pid_t{1002}));

// process B
utils::encapsulation::SharedMemory client(pid_t{1002});
client.init("/my-shm");
client.join();
auto messages = client.read();                               // id -> payloads

Errors

Code When
Pipe, Dup, Fork, Waitpid, Kill, Process Pipe / Dup / Process failures
EPoll epoll failures
Dlopen, Dlsym, Dlclose SharedObject
ShmOpen, Ftruncate, Fstat, Mmap, OutOfMemory, OutOfBounds, InvalidArgument, InvalidId SharedMemory (no free slot, message bigger than a slot, empty size...)

Clone this wiki locally