A collection of useful C++ classes and utility functions for systems programming.
Add this to your MODULE.bazel or WORKSPACE file:
http_archive(
name = "toolbelt",
urls = ["https://github.com/dallison/cpp_toolbelt/archive/refs/tags/A.B.C.tar.gz"],
strip_prefix = "cpp_toolbelt-A.B.C",
)Replace A.B.C with the version you want (e.g., 1.0.3).
Add a dependency to your BUILD.bazel targets:
deps = [
# ...
"@toolbelt//toolbelt",
],include(FetchContent)
FetchContent_Declare(
toolbelt
GIT_REPOSITORY https://github.com/dallison/cpp_toolbelt.git
GIT_TAG <version-tag>
)
FetchContent_MakeAvailable(toolbelt)
target_link_libraries(your_target PRIVATE toolbelt)A reference-counted wrapper around UNIX file descriptors. Automatically closes the file descriptor when all references are destroyed.
Header: toolbelt/fd.h
class FileDescriptor {
public:
FileDescriptor();
explicit FileDescriptor(int fd, bool owned = true);
FileDescriptor(const FileDescriptor &f);
FileDescriptor(FileDescriptor &&f);
void Close();
bool IsOpen() const;
bool IsATTY() const;
int RefCount() const;
struct pollfd GetPollFd();
bool Valid() const;
int Fd() const;
void SetFd(int fd, bool owned = true);
void Reset();
void Release();
void ForceClose();
bool IsNonBlocking() const;
absl::Status SetNonBlocking();
absl::Status SetCloseOnExec();
absl::StatusOr<ssize_t> Read(void* buffer, size_t length,
const co::Coroutine* c = nullptr);
absl::StatusOr<ssize_t> Write(const void* buffer, size_t length,
const co::Coroutine* c = nullptr);
};#include "toolbelt/fd.h"
// Create from an existing file descriptor
int fd = open("/path/to/file", O_RDONLY);
toolbelt::FileDescriptor file(fd);
// Copy creates a new reference (cheap, just increments ref count)
toolbelt::FileDescriptor file2 = file;
assert(file.RefCount() == 2);
// File descriptor is automatically closed when last reference is destroyed
file.Close(); // Still open, file2 still references it
// file2 goes out of scope -> file descriptor is closed
// Read from file descriptor
char buffer[1024];
auto result = file.Read(buffer, sizeof(buffer));
if (result.ok()) {
size_t bytes_read = *result;
// Process data...
}The socket classes provide a high-level interface to various socket types. They are coroutine-aware and work with the co coroutine library.
Header: toolbelt/sockets.h
Represents an IPv4 internet address and port.
class InetAddress {
public:
InetAddress();
InetAddress(int port); // INADDR_ANY with given port
InetAddress(const in_addr &ip, int port);
InetAddress(const std::string &hostname, int port);
InetAddress(const struct sockaddr_in &addr);
const sockaddr_in &GetAddress() const;
socklen_t GetLength() const;
bool Valid() const;
in_addr IpAddress() const; // Host byte order
int Port() const; // Host byte order
void SetPort(int port);
std::string ToString() const;
static InetAddress BroadcastAddress(int port);
static InetAddress AnyAddress(int port);
};#include "toolbelt/sockets.h"
// Create address from hostname and port
toolbelt::InetAddress addr("localhost", 8080);
// Create address for any interface on port 8080
auto any_addr = toolbelt::InetAddress::AnyAddress(8080);
// Get address components
in_addr ip = addr.IpAddress();
int port = addr.Port();
std::string str = addr.ToString(); // "127.0.0.1:8080"Represents a virtual socket address (VSOCK) used for communication between VMs and the host.
class VirtualAddress {
public:
VirtualAddress();
VirtualAddress(uint32_t port);
VirtualAddress(uint32_t cid, uint32_t port);
uint32_t Cid() const;
uint32_t Port() const;
void SetPort(uint32_t port);
std::string ToString() const;
static VirtualAddress HypervisorAddress(uint32_t port);
static VirtualAddress HostAddress(uint32_t port);
static VirtualAddress AnyAddress(uint32_t port);
#if defined(__linux__)
static VirtualAddress LocalAddress(uint32_t port);
#endif
};A variant type that can hold an InetAddress, VirtualAddress, or Unix socket path (string).
class SocketAddress {
public:
SocketAddress();
SocketAddress(const InetAddress &addr);
SocketAddress(const VirtualAddress &addr);
SocketAddress(const std::string &addr); // Unix socket path
const InetAddress &GetInetAddress() const;
const VirtualAddress &GetVirtualAddress() const;
const std::string &GetUnixAddress() const;
std::string ToString() const;
bool Valid() const;
int Type() const; // kAddressInet, kAddressVirtual, or kAddressUnix
int Port() const;
};Base class for all socket types. Provides common functionality for sending and receiving data.
class Socket {
public:
void Close();
bool Connected() const;
absl::StatusOr<ssize_t> Receive(char *buffer, size_t buflen,
const co::Coroutine *c = nullptr);
absl::StatusOr<ssize_t> Send(const char *buffer, size_t length,
const co::Coroutine *c = nullptr);
absl::StatusOr<ssize_t> ReceiveMessage(char *buffer, size_t buflen,
const co::Coroutine *c = nullptr);
absl::StatusOr<std::vector<char>> ReceiveVariableLengthMessage(
const co::Coroutine *c = nullptr);
absl::StatusOr<ssize_t> SendMessage(char *buffer, size_t length,
const co::Coroutine *c = nullptr);
absl::Status SetNonBlocking();
FileDescriptor GetFileDescriptor() const;
absl::Status SetCloseOnExec();
bool IsNonBlocking() const;
bool IsBlocking() const;
};A Unix Domain socket for inter-process communication.
class UnixSocket : public Socket {
public:
UnixSocket();
explicit UnixSocket(int fd, bool connected = false);
absl::Status Bind(const std::string &pathname, bool listen);
absl::Status Connect(const std::string &pathname);
absl::StatusOr<UnixSocket> Accept(const co::Coroutine *c = nullptr) const;
absl::Status SendFds(const std::vector<FileDescriptor> &fds,
const co::Coroutine *c = nullptr);
absl::Status ReceiveFds(std::vector<FileDescriptor> &fds,
const co::Coroutine *c = nullptr);
std::string BoundAddress() const;
absl::StatusOr<std::string> GetPeerName() const;
absl::StatusOr<std::string> LocalAddress() const;
};#include "toolbelt/sockets.h"
// Server side
toolbelt::UnixSocket server;
server.Bind("/tmp/mysocket", true); // true = listen
// In a coroutine or event loop
auto client = server.Accept(coroutine);
if (client.ok()) {
char buffer[1024];
auto result = client->Receive(buffer, sizeof(buffer), coroutine);
// Process received data...
}
// Client side
toolbelt::UnixSocket client;
client.Connect("/tmp/mysocket");
client.Send("Hello", 5, coroutine);A TCP socket for network communication.
class TCPSocket : public NetworkSocket {
public:
TCPSocket();
explicit TCPSocket(int fd, bool connected = false);
absl::Status Bind(const InetAddress &addr, bool listen);
absl::StatusOr<TCPSocket> Accept(const co::Coroutine *c = nullptr) const;
absl::StatusOr<InetAddress> LocalAddress(int port) const;
absl::StatusOr<InetAddress> GetPeerName() const;
};#include "toolbelt/sockets.h"
// Server
toolbelt::TCPSocket server;
toolbelt::InetAddress addr(8080);
server.Bind(addr, true); // Listen on port 8080
// Accept connections
auto client = server.Accept(coroutine);
if (client.ok()) {
char buffer[1024];
auto result = client->Receive(buffer, sizeof(buffer), coroutine);
}
// Client
toolbelt::TCPSocket client;
toolbelt::InetAddress server_addr("example.com", 8080);
client.Connect(server_addr);
client.Send("Hello", 5, coroutine);A UDP socket for datagram communication.
class UDPSocket : public NetworkSocket {
public:
UDPSocket();
explicit UDPSocket(int fd, bool connected = false);
absl::Status Bind(const InetAddress &addr);
absl::Status JoinMulticastGroup(const InetAddress &addr);
absl::Status LeaveMulticastGroup(const InetAddress &addr);
absl::Status SendTo(const InetAddress &addr, const void *buffer,
size_t length, const co::Coroutine *c = nullptr);
absl::StatusOr<ssize_t> Receive(void *buffer, size_t buflen,
const co::Coroutine *c = nullptr);
absl::StatusOr<ssize_t> ReceiveFrom(InetAddress &sender, void *buffer,
size_t buflen,
const co::Coroutine *c = nullptr);
absl::Status SetBroadcast();
absl::Status SetMulticastLoop();
};#include "toolbelt/sockets.h"
// Server
toolbelt::UDPSocket socket;
toolbelt::InetAddress addr(8080);
socket.Bind(addr);
toolbelt::InetAddress sender;
char buffer[1024];
auto result = socket.ReceiveFrom(sender, buffer, sizeof(buffer), coroutine);
if (result.ok()) {
// Process datagram from sender
}
// Client
toolbelt::UDPSocket socket;
toolbelt::InetAddress server("example.com", 8080);
socket.SendTo(server, "Hello", 5, coroutine);A virtual stream socket for VM-to-host communication using VSOCK.
class VirtualStreamSocket : public Socket {
public:
VirtualStreamSocket();
explicit VirtualStreamSocket(int fd, bool connected = false);
absl::Status Connect(const VirtualAddress &addr);
absl::Status Bind(const VirtualAddress &addr, bool listen);
absl::StatusOr<VirtualStreamSocket> Accept(
const co::Coroutine *c = nullptr) const;
absl::StatusOr<VirtualAddress> LocalAddress(uint32_t port) const;
const VirtualAddress &BoundAddress() const;
absl::StatusOr<VirtualAddress> GetPeerName() const;
uint32_t Cid() const;
};A type-erased wrapper that can hold a TCPSocket, VirtualStreamSocket, or UnixSocket. Useful when you need to work with different socket types uniformly.
class StreamSocket {
public:
StreamSocket();
absl::Status Bind(const SocketAddress &addr, bool listen);
absl::Status Connect(const SocketAddress &addr);
absl::StatusOr<StreamSocket> Accept(const co::Coroutine *c = nullptr) const;
TCPSocket &GetTCPSocket();
VirtualStreamSocket &GetVirtualStreamSocket();
UnixSocket &GetUnixSocket();
SocketAddress BoundAddress() const;
void Close();
bool Connected() const;
absl::StatusOr<ssize_t> Receive(char *buffer, size_t buflen,
const co::Coroutine *c = nullptr);
absl::StatusOr<ssize_t> Send(const char *buffer, size_t length,
const co::Coroutine *c = nullptr);
// ... other Socket methods
};#include "toolbelt/sockets.h"
// Works with any socket type
toolbelt::StreamSocket socket;
// Can bind to TCP, Unix, or VSOCK addresses
toolbelt::SocketAddress addr = toolbelt::InetAddress(8080);
// or: toolbelt::SocketAddress addr("/tmp/socket");
// or: toolbelt::SocketAddress addr(toolbelt::VirtualAddress(1234));
socket.Bind(addr, true);
auto client = socket.Accept(coroutine);A level-aware logger that supports colored output, multiple output modes, and themes.
Header: toolbelt/logging.h
enum class LogLevel {
kVerboseDebug,
kDebug,
kInfo,
kWarning,
kError,
kFatal,
};
enum class LogDisplayMode {
kPlain,
kColor,
kColumnar,
};
enum class LogTheme {
kDefault,
kLight,
kDark,
};
class Logger {
public:
Logger();
Logger(const std::string &subsystem, bool enabled = true,
LogTheme theme = LogTheme::kDefault,
LogDisplayMode mode = LogDisplayMode::kPlain);
Logger(LogLevel min);
void Enable();
void Disable();
absl::Status SetTeeFile(const std::string &filename, bool truncate = true);
void SetTeeStream(FILE *stream);
void Log(LogLevel level, const char *fmt, ...);
void VLog(LogLevel level, const char *fmt, va_list ap);
void Log(LogLevel level, uint64_t timestamp,
const std::string &source, std::string text);
void SetTheme(LogTheme theme);
void SetLogLevel(LogLevel l);
void SetLogLevel(const std::string &s); // "verbose", "debug", "info", etc.
LogLevel GetLogLevel() const;
void SetOutputStream(FILE *stream);
};#include "toolbelt/logging.h"
// Create logger with subsystem name
toolbelt::Logger logger("myapp", true, toolbelt::LogTheme::kDefault,
toolbelt::LogDisplayMode::kColor);
// Set minimum log level
logger.SetLogLevel(toolbelt::LogLevel::kInfo);
// or
logger.SetLogLevel("info");
// Log messages
logger.Log(toolbelt::LogLevel::kInfo, "Application started");
logger.Log(toolbelt::LogLevel::kDebug, "Debug value: %d", 42);
logger.Log(toolbelt::LogLevel::kWarning, "Warning: %s", "Something happened");
logger.Log(toolbelt::LogLevel::kError, "Error code: %d", errno);
// Tee output to a file
logger.SetTeeFile("/var/log/myapp.log");
// Disable logging temporarily
logger.Disable();
// ... do something ...
logger.Enable();RAII wrappers for pthread mutexes and read-write locks.
Header: toolbelt/mutex.h
class MutexLock {
public:
MutexLock(pthread_mutex_t *mutex);
~MutexLock();
};
class RWLock {
public:
RWLock(pthread_rwlock_t *lock, bool read);
~RWLock();
void ReadLock();
void WriteLock();
void Unlock();
};#include "toolbelt/mutex.h"
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
{
toolbelt::MutexLock lock(&mutex);
// Critical section - mutex is automatically unlocked when lock goes out of scope
// Do work...
}
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
{
toolbelt::RWLock lock(&rwlock, true); // true = read lock
// Multiple readers allowed
// Read data...
}
{
toolbelt::RWLock lock(&rwlock, false); // false = write lock
// Exclusive write access
// Write data...
}A fixed-size bitset that allows allocation of individual bits.
Header: toolbelt/bitset.h
template <int Size>
class BitSet {
public:
BitSet();
void Init();
absl::StatusOr<int> Allocate(const std::string &type);
bool IsEmpty() const;
void Set(int b);
void Clear(int b);
bool IsSet(int b) const;
};#include "toolbelt/bitset.h"
// Create a bitset with 256 bits
toolbelt::BitSet<256> bitset;
// Allocate the first available bit
auto result = bitset.Allocate("resource");
if (result.ok()) {
int bit_index = *result;
// Use the bit...
// Check if bit is set
if (bitset.IsSet(bit_index)) {
// ...
}
// Clear the bit when done
bitset.Clear(bit_index);
}
// Check if all bits are clear
if (bitset.IsEmpty()) {
// All resources freed
}A pipe for inter-process or inter-coroutine communication. Supports both blocking and non-blocking I/O, and can work with coroutines.
Header: toolbelt/pipe.h
class Pipe {
public:
static absl::StatusOr<Pipe> Create();
static absl::StatusOr<Pipe> CreateWithFlags(int flags);
static absl::StatusOr<Pipe> Create(int r, int w);
Pipe();
Pipe(int r, int w);
absl::Status Open(int flags = 0);
FileDescriptor &ReadFd();
FileDescriptor &WriteFd();
void SetReadFd(int fd);
void SetWriteFd(int fd);
void Close();
void ForceClose();
absl::Status SetNonBlocking(bool read, bool write);
absl::StatusOr<size_t> GetPipeSize();
absl::Status SetPipeSize(size_t size);
absl::StatusOr<ssize_t> Read(char *buffer, size_t length,
const co::Coroutine *c = nullptr);
absl::StatusOr<ssize_t> Write(const char *buffer, size_t length,
const co::Coroutine *c = nullptr);
};
template <typename T>
class SharedPtrPipe : public Pipe {
public:
static absl::StatusOr<SharedPtrPipe<T>> Create();
static absl::StatusOr<SharedPtrPipe<T>> Create(int r, int w);
absl::StatusOr<std::shared_ptr<T>> Read(const co::Coroutine *c = nullptr);
absl::Status Write(std::shared_ptr<T> p, const co::Coroutine *c = nullptr);
};#include "toolbelt/pipe.h"
// Create a pipe
auto pipe_result = toolbelt::Pipe::Create();
if (!pipe_result.ok()) {
// Handle error
return;
}
toolbelt::Pipe pipe = *pipe_result;
// Set non-blocking mode
pipe.SetNonBlocking(true, true);
// Write data (in a coroutine or thread)
char data[] = "Hello, World!";
auto write_result = pipe.Write(data, strlen(data), coroutine);
// Read data (in another coroutine or thread)
char buffer[1024];
auto read_result = pipe.Read(buffer, sizeof(buffer), coroutine);
if (read_result.ok()) {
size_t bytes_read = *read_result;
// Process data...
}
// SharedPtrPipe for sending shared_ptr objects (same process only)
auto shared_pipe = toolbelt::SharedPtrPipe<MyClass>::Create();
if (shared_pipe.ok()) {
auto obj = std::make_shared<MyClass>();
shared_pipe->Write(obj, coroutine);
auto received = shared_pipe->Read(coroutine);
if (received.ok()) {
std::shared_ptr<MyClass> obj = *received;
// Use object...
}
}A table formatter for displaying tabular data with colors and sorting.
Header: toolbelt/table.h
class Table {
public:
struct Cell {
std::string data;
color::Color color;
};
Table(const std::vector<std::string> titles, ssize_t sort_column = 0,
std::function<bool(const std::string &, const std::string &)> comp = nullptr);
void AddRow(const std::vector<std::string> cells);
void AddRow(const std::vector<std::string> cells, color::Color color);
void AddRowWithColors(const std::vector<Cell> cells);
void AddRow();
void SetCell(size_t col, Cell &&cell);
void Print(int width, std::ostream &os);
void Clear();
void SortBy(size_t column,
std::function<bool(const std::string &, const std::string &)> comp);
void SortBy(size_t column);
static Cell MakeCell(std::string data, color::Color color = {...});
};#include "toolbelt/table.h"
#include <iostream>
// Create table with column titles
toolbelt::Table table({"Name", "Age", "City"}, 0); // Sort by first column
// Add rows
table.AddRow({"Alice", "30", "New York"});
table.AddRow({"Bob", "25", "London"});
table.AddRow({"Charlie", "35", "Paris"});
// Add row with color
auto red = toolbelt::color::Color{.mod = toolbelt::color::kRed};
table.AddRow({"David", "40", "Tokyo"}, red);
// Add row with per-cell colors
std::vector<toolbelt::Table::Cell> cells = {
toolbelt::Table::MakeCell("Eve", toolbelt::color::Color{.mod = toolbelt::color::kGreen}),
toolbelt::Table::MakeCell("28"),
toolbelt::Table::MakeCell("Berlin")
};
table.AddRowWithColors(cells);
// Sort by age column (column 1)
table.SortBy(1, [](const std::string &a, const std::string &b) {
return std::stoi(a) < std::stoi(b);
});
// Print table
table.Print(80, std::cout);A memory buffer with built-in allocation and deallocation. Useful for message serialization and wire protocols. Supports both fixed-size and resizable buffers, with optional bitmap-based small block allocator.
Header: toolbelt/payload_buffer.h
using BufferOffset = uint32_t;
struct PayloadBuffer {
// Constructors
PayloadBuffer(uint32_t size, bool bitmap_allocator = true);
PayloadBuffer(uint32_t initial_size, Resizer r, bool bitmap_allocator = true);
// Allocation
static void *Allocate(PayloadBuffer **buffer, uint32_t n,
bool clear = true, bool enable_small_block = true);
void Free(void *p);
static void *Realloc(PayloadBuffer **buffer, void *p, uint32_t n,
bool clear = true, bool enable_small_block = true);
// String operations
static char *SetString(PayloadBuffer **self, const char *s, size_t len,
BufferOffset header_offset);
std::string GetString(BufferOffset header_offset) const;
std::string_view GetStringView(BufferOffset header_offset) const;
// Vector operations
template <typename T>
static void VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v,
bool enable_small_block = true);
template <typename T>
static void VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n,
bool enable_small_block = true);
template <typename T>
T VectorGet(const VectorHeader *hdr, size_t index) const;
// Address conversion
template <typename T = void>
T *ToAddress(BufferOffset offset, size_t size = 0);
template <typename T = void>
BufferOffset ToOffset(T *addr, size_t size = 0);
// Utilities
size_t Size() const;
void Dump(std::ostream &os);
bool IsValidMagic() const;
bool IsMoveable() const;
};#include "toolbelt/payload_buffer.h"
// Create a fixed-size buffer
char buffer_memory[4096];
toolbelt::PayloadBuffer *pb = new (buffer_memory) toolbelt::PayloadBuffer(4096);
// Allocate memory in the buffer
void *data = toolbelt::PayloadBuffer::Allocate(&pb, 100);
// Use data...
// Free memory
pb->Free(data);
// Create a resizable buffer
auto resizer = [](toolbelt::PayloadBuffer **b, size_t old_size, size_t new_size) {
char *new_mem = new char[new_size];
memcpy(new_mem, *b, old_size);
delete[] reinterpret_cast<char*>(*b);
*b = reinterpret_cast<toolbelt::PayloadBuffer*>(new_mem);
};
toolbelt::PayloadBuffer *resizable = new toolbelt::PayloadBuffer(1024, resizer);
// Allocate string
toolbelt::BufferOffset str_offset = 100;
toolbelt::PayloadBuffer::SetString(&resizable, "Hello", 5, str_offset);
std::string str = resizable->GetString(str_offset);
// Vector operations
toolbelt::VectorHeader vec_hdr = {0, 0};
toolbelt::PayloadBuffer::VectorPush<int>(&resizable, &vec_hdr, 42);
toolbelt::PayloadBuffer::VectorPush<int>(&resizable, &vec_hdr, 100);
int value = resizable->VectorGet<int>(&vec_hdr, 0); // Returns 42A file descriptor that can be used to trigger events. Implemented as an eventfd on Linux or a pipe on other systems.
Header: toolbelt/triggerfd.h
class TriggerFd {
public:
TriggerFd();
TriggerFd(const FileDescriptor &poll_fd, const FileDescriptor &trigger_fd);
absl::Status Open();
static absl::StatusOr<TriggerFd> Create();
static absl::StatusOr<TriggerFd> Create(const FileDescriptor &poll_fd,
const FileDescriptor &trigger_fd);
void Close();
void SetPollFd(FileDescriptor fd);
void SetTriggerFd(FileDescriptor fd);
void Trigger();
bool Clear(); // Clears trigger and returns true if it was triggered
FileDescriptor &GetPollFd();
FileDescriptor &GetTriggerFd();
void AddPollFd(std::vector<struct pollfd> &fds);
};#include "toolbelt/triggerfd.h"
#include <sys/poll.h>
// Create a trigger file descriptor
auto trigger_result = toolbelt::TriggerFd::Create();
if (!trigger_result.ok()) {
return;
}
toolbelt::TriggerFd trigger = *trigger_result;
// Add to poll set
std::vector<struct pollfd> fds;
trigger.AddPollFd(fds);
// In event loop
int result = poll(fds.data(), fds.size(), -1);
if (result > 0 && (fds[0].revents & POLLIN)) {
if (trigger.Clear()) {
// Trigger was set, handle event
}
}
// Trigger from another thread/coroutine
trigger.Trigger();Get the current monotonic time in nanoseconds.
Header: toolbelt/clock.h
uint64_t Now();#include "toolbelt/clock.h"
uint64_t start = toolbelt::Now();
// Do work...
uint64_t end = toolbelt::Now();
uint64_t elapsed_ns = end - start;
double elapsed_ms = elapsed_ns / 1e6;Dump memory in hexadecimal format.
Header: toolbelt/hexdump.h
void Hexdump(const void* addr, size_t length, FILE* out = stdout);#include "toolbelt/hexdump.h"
char buffer[64] = "Hello, World!";
toolbelt::Hexdump(buffer, sizeof(buffer));
// Output:
// 00000000 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 00 00 00 |Hello, World!...|
// ...Print the current stack trace.
Header: toolbelt/stacktrace.h
void PrintCurrentStack(std::ostream &os);#include "toolbelt/stacktrace.h"
#include <iostream>
void some_function() {
// Print stack trace
toolbelt::PrintCurrentStack(std::cerr);
}See LICENSE file for licensing information.