Peios Learn
Products
PePeios pkpekit PvProvium UDUniversal Directory TrTrail PrProject WiWispist
Using Peios Security Basics Technical Documentation Source
Using Peios Security Basics Technical Documentation Source
Peios

SDK basics

Single-page view · as markdown

What is the Peios SDK

Peios / Developing for Peios / SDK basics

The Peios SDK is how your own software talks to Peios.

Peios exposes its security world — identities, tokens, access checks, file security, the registry, and the audit/event stream — through a kernel interface built out of raw syscalls, ioctls, and packed byte buffers in the MS-DTYP wire formats. That interface is precise, but it is not something you want to hand-assemble from C. The SDK is the layer that lifts it into ordinary functions you can call: build a SID, open a token, run an access check, read a registry value, emit an event.

It is a C ABI, not a C-only library. The shipping product is a set of hand-written C headers and a shared object with a frozen application binary interface. Anything that can call C — Go via cgo, Rust via bindgen, Python via ctypes, Zig, C++ — can link it and get the same surface. The library happens to be implemented in Rust, but that is invisible across the boundary: callers see peios_* functions, <peios/*.h> headers, and errno.

Who it's for #

You want the SDK if you are building software that runs on Peios and needs to participate in its security model — a service that checks whether a caller may perform an action, a tool that reads or writes security descriptors, an agent that emits audit events, or anything that reads and writes the registry.

You do not need it to simply run on Peios. Ordinary POSIX programs run under Peios's Linux-compatibility surface without ever linking libpeios. The SDK is for programs that want to reach past POSIX and speak KACS, LCS, and KMES directly. If you are administering a running system rather than writing code against it, the Peios operator documentation is the place to start.

The three surfaces #

Peios's kernel boundary has three subsystems, and the SDK mirrors them one-to-one. Everything in the SDK belongs to one of these:

SubsystemWhat it isSDK headers
KACS — Kernel Access Control SubsystemIdentities, tokens, access decisions, file security, and process security. The heart of the model.<peios/security.h>, <peios/token.h>, <peios/access.h>, <peios/file.h>, <peios/process.h>
LCS — the registryA hierarchical, transactional, secured key/value store — Peios's system configuration database.<peios/registry.h>
KMES — the event systemThe msgpack-framed audit and event stream: emit events, consume them.<peios/msgpack.h>, <peios/event.h>

The umbrella header <peios.h> pulls in all of them; you can also include the individual concept headers for a tighter compile surface.

libpeios, librsi, and the substrate #

The SDK is two libraries on a shared foundation, each with its own role:

  • libpeios — the userspace C-ABI library for KACS, LCS, and KMES. It is the registry client and the whole of the access-control and event surface, and it is what the bulk of this documentation describes. Link -lpeios, include <peios.h>.
  • librsi — the library for implementing a registry source (a storage backend): the provider counterpart to libpeios's registry client. Where libpeios reads and writes the registry, librsi is what a program uses to be the thing that holds the data and answers the kernel. Link -lrsi, include <rsi.h>. See Registry sources.
  • peios-cabi — the internal C-ABI substrate both libraries are built on (the allocator wiring, the errno slot, the syscall/ioctl wrappers, the getxattr-style buffer helpers). You never link it directly; it is shared plumbing, documented here only so the conventions it sets make sense.

So the registry has two sides in this SDK: the client side in libpeios (open keys, read/write values) and the source side in librsi (back the keys and values, serve the kernel's requests). Most programs are clients; you reach for librsi only when you are providing storage.

Because the two libraries share one substrate, one error model, and one audience, they share one doc set. Learn the conventions once and they hold across both.

What this documentation promises #

This is user-facing documentation, not a specification. The authoritative byte-level contracts live in the PCSA specifications — PCDS for the core data structures, PGSS, PSPK and PSPU for the interfaces — and in the <pkm/*.h> kernel headers; where a detail is normative, this documentation points you at the specification that owns it. Where a detail is about this implementation rather than the contract, it points at the Peios Kernel manual instead. What you get here is the working knowledge to use the library well: what every function does, what it returns, how memory and errors flow, and how the pieces fit together — with enough coverage that you should not need to open the library's source to answer a question. When you find a gap, that is a documentation bug worth reporting.

Where to go next #

  • Installing and linking — the packages, the headers, and how to build against the library.
  • Library conventions — the error model, the memory rules, and the buffer protocol that every function in the SDK follows. Read this once; it saves you re-learning it per module.
  • Your first program — a small, complete program you can compile and run.

Installing and linking

Peios / Developing for Peios / SDK basics

libpeios ships as a small set of packages, split the same way a C library conventionally is: a lean runtime package that programs depend on, and a development package that carries the headers and the linker symlink. You install the runtime everywhere the library is used and the development package only where you compile.

The packages #

PackageContentsWhen you need it
libpeiosThe versioned shared object libpeios.so.0 (the runtime soname).At runtime, on every machine that runs a program linked against the library. Pulled in automatically as a dependency of anything built against it.
libpeios-develThe public headers (peios.h + peios/*.h), the unversioned libpeios.so linker symlink, and the peios.pc pkg-config descriptor. Depends on a matching libpeios.At build time, on machines where you compile.
libpeios-staticThe static archive libpeios.a.Only if you link the library statically instead of against the shared object.
libpeios-debuginfoSplit DWARF debug info, build-id indexed.Debugging or profiling through the library.
libpeios-debugsourceThe referenced Rust sources for the debug info.Stepping into the library's own source in a debugger.

Installing libpeios-devel pulls in the matching libpeios runtime automatically — the development package pins the exact runtime version whose ABI its headers describe, so the headers you compile against and the shared object you load can never disagree.

The headers #

Everything lives under <peios/…>, with one umbrella:

#include <peios.h>          /* the whole API */

or, for a tighter compile surface, just the concept headers you use:

#include <peios/security.h> /* SIDs, security descriptors, ACLs */
#include <peios/token.h>    /* tokens */
#include <peios/access.h>   /* access checks */
#include <peios/file.h>     /* file security */
#include <peios/process.h>  /* process security */
#include <peios/registry.h> /* the LCS registry */
#include <peios/msgpack.h>  /* msgpack framing */
#include <peios/event.h>    /* the KMES event stream */

The headers are hand-written and are the real API — they carry the prose docs and the layout notes that a generated header can't. (Internally they are checked against the library's Rust surface by a cbindgen-based verifier, so they cannot silently drift from what the shared object actually exports. You do not interact with that machinery; it just means the header you read is the contract you get.)

The <pkm/*.h> dependency #

The Peios headers do not re-invent the kernel's wire constants — they use them directly. So <peios/security.h> includes <pkm/sid.h> and <pkm/sd.h>, and the other headers pull in their matching <pkm/*.h> UAPI headers for the KACS_*, LCS_*, and KMES_* constants and the #[repr(C)] argument structs. Those PKM kernel UAPI headers must be on your include path when you compile. They ship with the Peios kernel headers; on a normal Peios development install they are already where the toolchain looks. If a build fails with fatal error: pkm/sid.h: No such file or directory, that is what is missing — add the kernel UAPI include directory to your compiler's search path.

Compiling and linking #

With pkg-config (recommended) #

The development package installs a peios.pc descriptor, so pkg-config knows the include and library flags:

cc myprog.c $(pkg-config --cflags --libs peios) -o myprog

pkg-config --cflags peios expands to the include flags and --libs peios to -lpeios plus the library directory. This is the form to prefer — it stays correct across install prefixes and multiarch library directories.

By hand #

If you are not using pkg-config, link against -lpeios directly:

cc myprog.c -lpeios -o myprog

Add -I / -L flags if your headers and library live outside the compiler's default search paths.

Statically #

Install libpeios-static and point the linker at the archive:

cc myprog.c -o myprog /usr/lib/x86_64-linux-peios/libpeios.a

The static archive is also what the library's own integration tests link against, so it is a fully supported way to build. Static linking folds the library into your binary — you then do not need the libpeios runtime package on the target, though you still need whatever C runtime your program uses.

Linking from other languages #

Because libpeios is a C ABI, any language with a C FFI can call it. The shape is always the same: point the FFI at the libpeios.so.0 shared object (or the static archive), declare the peios_* functions with their C signatures, and follow the library conventions for buffers and errors exactly as a C caller would.

  • Rust — use the maintained bindings: the safe peios crate (or the raw peios-sys), covered in Using the SDK from Rust. You don't need to hand-roll bindgen. (The library is Rust internally, but the crates still bind it strictly as a C library through the stable ABI — the supported entry point.)
  • Go — cgo against <peios.h> with #cgo pkg-config: peios.
  • Python — ctypes or cffi against libpeios.so.0.
  • C++ — include the headers directly; they are wrapped in extern "C" and compile as C++.

The one rule that matters across every language: the errno-based error model and the caller-buffer / two-call buffer protocol are part of the contract, not a C convenience. Read Library conventions before you wrap the API in another language's idioms.

librsi — the registry-source library #

Everything above describes libpeios. If you are writing a registry source, you link the sibling library librsi instead (or as well). It is built on the same peios-cabi substrate, follows the identical library conventions — raw fds, the int/ssize_t error model, the borrow discipline — and is packaged exactly the same way libpeios is.

The packages #

PackageContentsWhen you need it
librsiThe versioned shared object librsi.so.0.At runtime, wherever a source runs.
librsi-develThe public headers (rsi.h + rsi/*.h), the librsi.so linker symlink, and the rsi.pc pkg-config descriptor. Depends on a matching librsi.At build time.
librsi-staticThe static archive librsi.a.Only for static linking.
librsi-debuginfoSplit DWARF debug info.Debugging or profiling through the library.
librsi-debugsourceThe referenced Rust sources.Stepping into the library's own source.

As with libpeios, librsi-devel pulls in the matching librsi runtime and the kernel-headers package — the <rsi/*.h> headers include <pkm/lcs.h>, so the same UAPI-header requirement described above applies.

Headers and linking #

Include the umbrella or the individual concept headers:

#include <rsi.h>            /* the whole librsi API */
/* or: */
#include <rsi/source.h>    /* registration */
#include <rsi/request.h>   /* decoding requests */
#include <rsi/response.h>  /* building responses */

and compile with pkg-config (the descriptor's module name is rsi):

cc mysource.c $(pkg-config --cflags --libs rsi) -o mysource

or link -lrsi directly, or against librsi.a for a static build — exactly the three forms shown for libpeios above. The two libraries are independent: a program can be a client (libpeios), a source (librsi), or both, linking each as needed.

Your first program

Peios / Developing for Peios / SDK basics

This is a complete program you can compile and run. It does not touch the kernel — it works entirely with the security-descriptor vocabulary from <peios/security.h>, which makes it a safe first outing: no privileges, no live tokens, just the conventions from Library conventions put to work.

The task: take a security descriptor written in SDDL text, turn it into wire bytes, read its owner, and print that owner's SID in string form. Along the way you exercise the two-call buffer protocol twice, parse with a zero-copy view, and handle errors the libpeios way.

The program #

#include <peios/security.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    /* An SDDL descriptor: owner and group are Administrators (BA), with a
       DACL granting them full access (FA). */
    const char *sddl = "O:BAG:BAD:(A;;FA;;;BA)";

    /* 1. Parse the SDDL text into self-relative SD wire bytes.
          First call probes for the size (cap == 0), then we retrieve. */
    ssize_t need = peios_sddl_parse_sd(NULL, 0, sddl);
    if (need < 0) {
        fprintf(stderr, "parse probe failed: %s\n", strerror(errno));
        return 1;
    }

    unsigned char *sd = malloc((size_t)need);
    if (!sd)
        return 1;

    ssize_t sd_len = peios_sddl_parse_sd(sd, (size_t)need, sddl);
    if (sd_len < 0) {
        fprintf(stderr, "parse failed: %s\n", strerror(errno));
        free(sd);
        return 1;
    }
    printf("security descriptor: %zd bytes\n", sd_len);

    /* 2. Parse the SD into a zero-copy view. The view borrows `sd` — it
          must stay alive and unmodified until we are done reading. */
    peios_sd_view view;
    if (peios_sd_parse(sd, (size_t)sd_len, &view) != 0) {
        fprintf(stderr, "sd parse failed: %s\n", strerror(errno));
        free(sd);
        return 1;
    }

    /* 3. Pull out the owner SID. On success `owner` points INTO `sd`. */
    const void *owner;
    size_t owner_len;
    if (peios_sd_view_owner(&view, &owner, &owner_len) != 0) {
        printf("no owner set\n");
        free(sd);
        return 0;
    }

    /* 4. Format the owner SID as its "S-1-…" string. The byte form of a
          SID is bounded, but its string form is variable, so we probe.
          A string length excludes the NUL, so allocate len + 1. */
    ssize_t slen = peios_sid_format(owner, owner_len, NULL, 0);
    if (slen < 0) {
        fprintf(stderr, "sid format probe failed: %s\n", strerror(errno));
        free(sd);
        return 1;
    }

    char *str = malloc((size_t)slen + 1);
    if (!str) {
        free(sd);
        return 1;
    }
    if (peios_sid_format(owner, owner_len, str, (size_t)slen + 1) < 0) {
        fprintf(stderr, "sid format failed: %s\n", strerror(errno));
        free(str);
        free(sd);
        return 1;
    }

    printf("owner: %s\n", str);

    /* 5. Only now free `sd` — every pointer the view gave us pointed into
          it, so it had to outlive the last read. */
    free(str);
    free(sd);
    return 0;
}

Building and running #

cc first.c $(pkg-config --cflags --libs peios) -o first
./first

Expected output:

security descriptor: 44 bytes
owner: S-1-5-32-544

S-1-5-32-544 is the well-known SID for the local Administrators group — which is exactly the BA you wrote in the SDDL string. (The byte count may differ across versions; the owner will not.)

What just happened #

Every convention from the previous page showed up here:

  • The two-call protocol, twice. peios_sddl_parse_sd and peios_sid_format were each called first with a NULL/0 buffer to learn the size, then again to fill a right-sized allocation. Neither could ever truncate: a too-small buffer would have returned -1 with ERANGE, not a partial result.
  • A string length excludes the NUL. That is why the SID string buffer was slen + 1.
  • A zero-copy view borrowed our buffer. owner pointed into sd, so sd had to stay alive and unmodified until after the final peios_sid_format. Freeing it earlier would have left owner dangling — the one mistake this pattern invites, and the reason step 5 frees last.
  • Errors came back through the return value and errno. Every call was checked; strerror(errno) explained any failure. No exceptions, no out-of-band error channel.

A shortcut worth knowing #

Not every buffer needs a probe. Some results have a known ceiling, and the library gives you a constant so you can use a fixed stack buffer and skip the first call entirely. A SID is the classic case — it is never larger than PEIOS_SID_MAX_BYTES:

unsigned char sid[PEIOS_SID_MAX_BYTES];
ssize_t n = peios_sid_well_known(sid, sizeof sid, PEIOS_WKS_ADMINISTRATORS);
/* n > 0: `sid` holds S-1-5-32-544 in wire form, no malloc, no probe. */

Use the probe when a length is genuinely unbounded (strings, whole descriptors, ACLs); use the fixed-size shortcut when the module documents a ceiling for the thing you are building.

Where to go next #

You now have the mechanics. From here, pick the subsystem you need:

  • Access control (KACS) — the security vocabulary you just used, plus tokens, access checks, file security, and process security.
  • The registry (LCS) — reading and writing Peios's configuration store.
  • Events (KMES) — emitting and consuming the audit/event stream.

Using the SDK from Rust

Peios / Developing for Peios / SDK basics

The SDK is a C ABI, so it is reachable from any language with a C FFI — but Rust gets first-class, maintained bindings rather than hand-rolled extern blocks. If you are writing Rust on Peios, use them. This page is the on-ramp: what the crates are, how to wire them into a build, and where the docs live. It is deliberately not an API reference — that job belongs to the crate's rustdoc, which is generated from the code and so never drifts from it.

The two crates #

peios-rs is two crates, layered:

CrateWhat it isUse it when
peios-sysRaw, unsafe FFI. Bindings are generated at build time by bindgen from the hand-written <peios.h> (the shipping API, which the ABI verifier proves is identical to the Rust source).You need the raw C surface — an escape hatch, or to build your own abstractions.
peiosThe safe, idiomatic wrapper: RAII handle types, Result-returning methods, typed wire-constant families, and owned buffers — so you never touch a raw fd, a sticky-error builder, or a getxattr-style size probe directly.Almost always. This is the crate you want.

Both bind libpeios strictly as a C library through its stable ABI — they never depend on libpeios's internal Rust crates. The C ABI is the only supported entry point, and binding through it means the crates exercise the exact surface every other consumer uses.

The safe crate's modules mirror the libpeios concept headers one-for-one — security, token, access, file, process, event, msgpack, registry — so everything you learn about the C surface maps straight across.

Where the docs live #

Two places, and the split is deliberate:

  • The API reference is the crate's rustdoc. Every public item in peios is documented (the crate is built with #![warn(missing_docs)], so this is enforced), with a crate-level overview, per-module docs, and the error model. Build and browse it locally with:

    cargo doc -p peios --open
    

    This is the reference precisely because it is generated from the code — it can never fall out of sync with the actual method signatures and types the way hand-written prose would.

  • The concepts live in these docs. The what and why — what a token is, how the two-call protocol works, how an access check reaches a verdict, the RSI source model — are identical whether you call from C or Rust, and they are documented once, here. The safe crate mirrors the concept headers, so each learn section maps to a module:

    Rust moduleConcepts in
    security, token, access, file, processAccess control
    registryThe registry
    event, msgpackEvents

    Read the concept page for the model, then the rustdoc for the exact Rust API.

Adding the dependency #

Add the peios crate to your Cargo.toml:

[dependencies]
peios = { git = "https://github.com/peios/peios-rs" }

(or a path/registry dependency, however you consume Peios crates). The features:

FeatureEffect
defaultDynamic linking against libpeios.so — the intended production model (one soname-versioned system copy; fixes ship once).
staticStatic linking against libpeios.a. See the caveat below.
uapiReuse the canonical Rust mirror of the pkm UAPI types (peios-uapi) instead of letting bindgen emit its own copy, so the kacs_*/pkm types are one identity across crates.

A first taste #

The safe crate turns the C conventions into ordinary Rust. Compare this with the C first program — no size probes, no manual close, errors as Result:

use peios::token::{Token, TokenAccess};

fn main() -> peios::Result<()> {
    // Open my own token; the handle closes on drop.
    let me = Token::open_self(false, TokenAccess::QUERY)?;

    let sid = me.user()?;          // owned Sid, not a caller buffer + length
    let il  = me.integrity()?;     // typed IntegrityLevel, not a raw u32
    println!("running as {sid} at integrity {il:?}");

    Ok(())
}

The idiomatic wins show up most in the fiddly places. Impersonation, for instance, is an RAII guard — identity reverts when the guard drops, so it is exception-safe by construction rather than needing a manual revert in every cleanup path:

use std::os::fd::BorrowedFd;

fn serve(conn: BorrowedFd<'_>) -> peios::Result<()> {
    let caller = Token::open_peer(conn)?;
    let _guard = caller.impersonate_scoped()?;   // now acting as the caller
    do_work_as_caller()?;                        // any early return still reverts
    Ok(())
    // `_guard` drops here (or on the `?` above) → identity reverts automatically
}

Success-side status values — a file's opened-vs-created disposition, a key's created-new-vs-opened — come back alongside the handle, not as errors; genuine failures are the Err(Error) side, where Error wraps the errno the C ABI set.

Linking: dynamic vs static #

This is the one Rust-specific wrinkle worth understanding, because the static path has a sharp edge.

  • Dynamic (default) links libpeios.so — the production model, and the path a normal std Rust program must use.
  • Static (--features static) links libpeios.a. That archive is a Rust staticlib: it bakes in its own copy of the Rust runtime (the panic = "abort" handler, the global-allocator shim, the alloc-error handler). Linking it into a consumer that also carries that runtime — i.e. any Rust binary that links std — collides on those symbols (rust_begin_unwind, __rust_alloc_error_handler, …). So the static path is for C consumers and no_std Rust binaries that supply no conflicting runtime. A std Rust consumer — including cargo test — must use the dynamic path.

Keep that rule in mind and static linking is a non-issue: reach for it only from no_std, and use the default dynamic link everywhere else.

Finding libpeios at build time #

peios-sys's build script resolves the library and headers in this order:

  1. Environment override — set all three:
    • PEIOS_LIB_DIR — the directory containing libpeios.{so,a}
    • PEIOS_INCLUDE — the directory containing <peios.h>
    • PKM_UAPI — the directory containing <pkm/*.h> (referenced by peios.h's signatures)
  2. pkg-config — otherwise, if libpeios installs its peios.pc on PKG_CONFIG_PATH.

On a system where libpeios is installed from its packages, pkg-config just works and you set nothing. For a local checkout, point the three variables at your build tree:

PEIOS_LIB_DIR=../libpeios/target/release \
PEIOS_INCLUDE=../libpeios/include \
PKM_UAPI=../pkm/uapi \
cargo build

One bring-up gotcha for local dynamic builds: the crate bakes an rpath that resolves libpeios by its soname (libpeios.so.0), but a plain cargo build of libpeios emits only the unversioned libpeios.so. Create the soname link once so the run resolves:

ln -sf libpeios.so ../libpeios/target/release/libpeios.so.0

(This is a checkout convenience — on a real system the packaged libpeios.so.0 is already there.)

The bottom line #

  • Depend on the peios crate; drop to peios-sys only for the raw surface.
  • Concepts: these docs. API reference: cargo doc -p peios --open.
  • Use the default dynamic link unless you are no_std.

Everything the library conventions page teaches still holds underneath — the crate just wraps it in Rust idiom.

Peios Learn — documentation for the Peios project.

Built with Trail.