# SDK basics

---

# What is the Peios SDK

_Peios / Developing for Peios / SDK basics_

> The Peios SDK is the C-ABI library family you use to talk to Peios from your own programs — access control, the registry, and events. Its first and largest member is libpeios.

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](/peios/security-fundamentals/identity/overview.md) 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:

| Subsystem | What it is | SDK headers |
|---|---|---|
| **KACS** — Kernel Access Control Subsystem | Identities, 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 registry | A hierarchical, transactional, secured key/value store — Peios's system configuration database. | `<peios/registry.h>` |
| **KMES** — the event system | The 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/developing-for-peios/registry-sources/overview.md).
- **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](/peios/developing-for-peios/sdk-basics/installing-and-linking.md)** — the packages, the headers, and how to build against the library.
- **[Library conventions](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md)** — 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](/peios/developing-for-peios/sdk-basics/your-first-program.md)** — a small, complete program you can compile and run.

---

# Installing and linking

_Peios / Developing for Peios / SDK basics_

> The packages that make up libpeios, the headers you include, and how to compile and link against the library — with pkg-config, by hand, statically, and from non-C languages.

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

| Package | Contents | When you need it |
|---|---|---|
| `libpeios` | The 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-devel` | The 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-static` | The static archive `libpeios.a`. | Only if you link the library **statically** instead of against the shared object. |
| `libpeios-debuginfo` | Split DWARF debug info, build-id indexed. | Debugging or profiling through the library. |
| `libpeios-debugsource` | The 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:

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

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

```c
#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:

```sh
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:

```sh
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:

```sh
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](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md) 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](/peios/developing-for-peios/sdk-basics/using-the-sdk-from-rust.md). 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](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md) 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](/peios/developing-for-peios/registry-sources/overview.md), you link the sibling library **librsi** instead (or as well). It is built on the same [`peios-cabi` substrate](/peios/developing-for-peios/sdk-basics/what-is-the-peios-sdk.md), follows the identical [library conventions](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md) — raw fds, the `int`/`ssize_t` error model, the borrow discipline — and is packaged exactly the same way libpeios is.

### The packages

| Package | Contents | When you need it |
|---|---|---|
| `librsi` | The versioned shared object `librsi.so.0`. | At **runtime**, wherever a source runs. |
| `librsi-devel` | The 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-static` | The static archive `librsi.a`. | Only for static linking. |
| `librsi-debuginfo` | Split DWARF debug info. | Debugging or profiling through the library. |
| `librsi-debugsource` | The 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:

```c
#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`):

```sh
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_

> A small, complete C program that parses a security descriptor, reads its owner, and formats the SID back to text — putting the two-call protocol, views, and the error model to work.

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](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md) 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

```c
#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

```sh
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`:

```c
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)](/peios/developing-for-peios/sdk-access-control/overview.md)** — the security vocabulary you just used, plus tokens, access checks, file security, and process security.
- **[The registry (LCS)](/peios/developing-for-peios/sdk-registry/overview.md)** — reading and writing Peios's configuration store.
- **[Events (KMES)](/peios/developing-for-peios/sdk-events/overview.md)** — emitting and consuming the audit/event stream.

---

# Using the SDK from Rust

_Peios / Developing for Peios / SDK basics_

> The Peios SDK ships first-class Rust bindings — the peios-sys raw FFI crate and the safe, idiomatic peios wrapper. This page is the on-ramp; the crate's rustdoc is the API reference.

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:

| Crate | What it is | Use it when |
|---|---|---|
| **`peios-sys`** | Raw, 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. |
| **`peios`** | The 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:

  ```sh
  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](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md#the-two-call-buffer-protocol) works, how an [access check](/peios/developing-for-peios/sdk-access-control/checking-access.md) reaches a verdict, the [RSI source model](/peios/developing-for-peios/registry-sources/overview.md) — 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 module | Concepts in |
  |---|---|
  | `security`, `token`, `access`, `file`, `process` | [Access control](/peios/developing-for-peios/sdk-access-control/overview.md) |
  | `registry` | [The registry](/peios/developing-for-peios/sdk-registry/overview.md) |
  | `event`, `msgpack` | [Events](/peios/developing-for-peios/sdk-events/overview.md) |

  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`:

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

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

| Feature | Effect |
|---|---|
| *default* | Dynamic linking against `libpeios.so` — the intended production model (one soname-versioned system copy; fixes ship once). |
| `static` | Static linking against `libpeios.a`. See the [caveat below](#linking-dynamic-vs-static). |
| `uapi` | Reuse 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](/peios/developing-for-peios/sdk-basics/your-first-program.md) — no size probes, no manual `close`, errors as `Result`:

```rust
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:

```rust
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](/peios/developing-for-peios/sdk-basics/installing-and-linking.md#the-packages), pkg-config just works and you set nothing. For a **local checkout**, point the three variables at your build tree:

```sh
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:

```sh
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](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md) page teaches still holds underneath — the crate just wraps it in Rust idiom.
