operations capa

operations-jail-inventory

FreeBSD jail estate inventory: jails, ZFS datasets, snapshots and listening sockets, and the disagreements between them

FreeBSD jail estate inventory: jails, ZFS datasets, snapshots and listening sockets, and the disagreements between them

Tieroperations
Roleinfrastructure adapter
Pathcrates/operations/jail-inventory
Edition2021
Targetsestate-report, operations_jail_inventory, estate
Public items37 across 5 modules
Tests24

What it is for

FreeBSD jail estate inventory: what a host actually holds, and where its own records disagree with its disks.

operations_host_inventory answers what the hardware is. This crate answers what the software estate is: the jails, the ZFS datasets and snapshots beneath them, what is listening, and — the part no existing tool answered — which datasets belong to no jail.

What running it against a real host taught it

The first version of this crate, driven only by fixtures, reported 22 orphaned datasets on a live FreeBSD 13.2 host. One was real — a 7 GB dataset whose jail had not existed for years. Of the rest, five were the boot pool's own system datasets and fourteen held virtual machine disks, two of them belonging to running virtual machines. An earlier claim that the question was worth "roughly 110 GB" came from that bad reading and is false; the interesting finding is the other one, that most of a jail host's unexplained space is usually explained, and a tool that cannot say by what will recommend deleting a running machine.

That is why a reconciliation here reports three things and not one: what is wrong (Disagreement), what is claimed by something other than a jail (DatasetClaim), and what it could not classify at all (Reconciliation::unclassified). An empty orphan list must never mean both "nothing to reclaim" and "I could not read this host".

Why the parsing lives here and the running does not

Every reader in this crate takes text and returns data. Nothing here runs a command by itself; a CommandRunner is passed in, and its subprocess calls use strict argument vectors — never a shell string. That rule is inherited from operations-host-inventory deliberately: three separate failures in one session came from shell strings being re-split by an intermediate shell, including a ps -axo user=,rss=,command= that silently returned one column because = swallowed the rest of the argument as a header. Argument vectors make that class impossible.

Capabilities

crate root

FreeBSD jail estate inventory: what a host actually holds, and where its own records

Item
fn strip_ansi(text : & str) -> String

EstateError

FreeBSD jail estate inventory: what a host actually holds, and where its own records

Item
pub enum EstateError

Result

FreeBSD jail estate inventory: what a host actually holds, and where its own records

Item
pub type Result<T>: std::result::Result <T, EstateError>

datasets (other)

ZFS datasets and snapshots, as zfs list reports them.

Item
pub const ZFS_COLUMNS: & str

Dataset

ZFS datasets and snapshots, as zfs list reports them.

Item
pub struct Dataset
Dataset :: fn used_by_snapshots(& self) -> Option <& str>
fn parse(output : & str) -> Result <Vec <Dataset>>
fn detect <R : CommandRunner>(runner : & R) -> Result <Vec <Dataset>>

Snapshot

ZFS datasets and snapshots, as zfs list reports them.

Item
pub struct Snapshot
fn parse_snapshots(output : & str) -> Result <Vec <Snapshot>>

jails (other)

The jails a host is running, as jls reports them.

Item
pub const JLS_COLUMNS: & str; 5

ConfiguredJails

The jails a host is running, as jls reports them.

Item
pub struct ConfiguredJails
ConfiguredJails :: fn from_names <I, S>(names : I) -> Self where I : IntoIterator <Item = S>, S : Into <String>,
ConfiguredJails :: fn contains(& self, name : & str) -> bool
ConfiguredJails :: fn names(& self) -> & String
ConfiguredJails :: fn len(& self) -> usize
ConfiguredJails :: fn is_empty(& self) -> bool
fn configured <R : CommandRunner>(runner : & R) -> Result <ConfiguredJails>

Jail

The jails a host is running, as jls reports them.

Item
pub struct Jail
fn parse(output : & str) -> Result <Vec <Jail>>
fn detect <R : CommandRunner>(runner : & R) -> Result <Vec <Jail>>

Listener

What is listening, as sockstat reports it.

Item
pub struct Listener
fn parse(output : & str) -> Result <Vec <Listener>>
fn detect <R : CommandRunner>(runner : & R, jid : & str) -> Result <Vec <Listener>>

reconcile (other)

Where a host's records and its disks disagree — and, just as importantly, what it could

Item
fn reconcile(estate : & Estate, configured : & ConfiguredJails, layout : & JailLayout,) -> Reconciliation

Claim

Where a host's records and its disks disagree — and, just as importantly, what it could

Item
pub enum Claim

DatasetClaim

Where a host's records and its disks disagree — and, just as importantly, what it could

Item
pub struct DatasetClaim

Disagreement

Where a host's records and its disks disagree — and, just as importantly, what it could

Item
pub enum Disagreement

Estate

Where a host's records and its disks disagree — and, just as importantly, what it could

Item
pub struct Estate

JailLayout

Where a host's records and its disks disagree — and, just as importantly, what it could

Item
pub struct JailLayout
JailLayout :: fn default() -> Self

Reconciliation

Where a host's records and its disks disagree — and, just as importantly, what it could

Item
pub struct Reconciliation

testkit (other)

Fixtures captured from a real FreeBSD 13.2 jail host on 2026-09-11.

Item
pub const JLS: & str
pub const ZFS_LIST: & str
pub const ZFS_SNAPSHOTS: & str
pub const SOCKSTAT: & str
pub const CONFIGURED: & str; 5

How to use it

From `examples/estate-report.rs`:


use std::{fs, process};

use operations_jail_inventory::{
    datasets, jails,
    reconcile::{self, Claim, Disagreement, Estate, JailLayout},
    strip_ansi, ConfiguredJails,
};

fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    let [jls_path, zfs_path, configured_path] = match args.as_slice() {
        [a, b, c] => [a, b, c],
        _ => {
            eprintln!("usage: estate-report <jls.txt> <zfs-list.txt> <configured.txt>");
            process::exit(2);
        }
    };

Module structure

operations_jail_inventory

flowchart TD
  n_operations_jail_inventory["operations_jail_inventory"]
  n_operations_jail_inventory --> n_datasets["datasets"]
  n_operations_jail_inventory --> n_jails["jails"]
  n_operations_jail_inventory --> n_listeners["listeners"]
  n_operations_jail_inventory --> n_reconcile["reconcile"]
  n_operations_jail_inventory --> n_testkit["testkit"]

Public surface

`crate root`

ItemWhat it is
pub enum EstateErrorReading an estate can fail in ways worth telling apart: the command was refused, or it answered in a shape we do not understand
pub type Result<T>: std::result::Result <T, EstateError>The result of reading one part of an estate.
fn strip_ansi(text : & str) -> StringRemove ANSI colour escapes from captured output

`datasets`

ItemWhat it is
pub struct DatasetOne ZFS dataset.
pub struct SnapshotOne snapshot.
Dataset :: fn used_by_snapshots(& self) -> Option <& str>Space returned if every snapshot of this dataset were destroyed
pub const ZFS_COLUMNS: & strThe columns this crate asks zfs list for.
fn parse(output : & str) -> Result <Vec <Dataset>>Parse zfs list -H -o name,used,mountpoint: tab-separated, no header.
fn parse_snapshots(output : & str) -> Result <Vec <Snapshot>>Parse zfs list -H -t snapshot -o name,used.
fn detect <R : CommandRunner>(runner : & R) -> Result <Vec <Dataset>>Read the datasets from a host.

`jails`

ItemWhat it is
pub struct JailOne running jail.
pub const JLS_COLUMNS: & str; 5The column order this crate asks jls for
fn parse(output : & str) -> Result <Vec <Jail>>Parse jls -h jid name path host.hostname ip4.addr: a header line naming the columns, then one row per jail
pub struct ConfiguredJailsThe jails configured on one host, running or not
ConfiguredJails :: fn from_names <I, S>(names : I) -> Self where I : IntoIterator <Item = S>, S : Into <String>,Build from names the caller vouches for as belonging to one host
ConfiguredJails :: fn contains(& self, name : & str) -> boolWhether this host configures a jail of that name.
ConfiguredJails :: fn names(& self) -> & StringThe names, in the order the host gave them.
ConfiguredJails :: fn len(& self) -> usizeHow many jails this host configures.
ConfiguredJails :: fn is_empty(& self) -> boolWhether the host configures no jails at all.
fn configured <R : CommandRunner>(runner : & R) -> Result <ConfiguredJails>Read the jails configured on this host from cbsd
fn detect <R : CommandRunner>(runner : & R) -> Result <Vec <Jail>>Read the running jails from a host.

`listeners`

ItemWhat it is
pub struct ListenerOne listening socket.
fn parse(output : & str) -> Result <Vec <Listener>>Parse sockstat -j <jid> -l: a header line, then one row per socket
fn detect <R : CommandRunner>(runner : & R, jid : & str) -> Result <Vec <Listener>>Read the listening sockets of one jail.

`reconcile`

ItemWhat it is
pub struct EstateOne host's estate, as read.
pub struct JailLayoutHow a host lays its jails out on disk
JailLayout :: fn default() -> Self
pub enum DisagreementA problem
pub enum ClaimWhat claims a dataset, when the claimant is not a jail
pub struct DatasetClaimOne dataset and what claims it.
pub struct ReconciliationThe whole answer: problems, claims, and what could not be read.
fn reconcile(estate : & Estate, configured : & ConfiguredJails, layout : & JailLayout,) -> ReconciliationCompare the jails against the datasets and report everything found

`testkit`

ItemWhat it is
pub const JLS: & strjls -h jid name path host.hostname ip4.addr
pub const ZFS_LIST: & strzfs list -H -o name,used,mountpoint, tab-separated
pub const ZFS_SNAPSHOTS: & strzfs list -H -t snapshot -o name,used
pub const SOCKSTAT: & strsockstat -j 55 -l — the wiki jail, after its httpd was started.
pub const CONFIGURED: & str; 5The jails cbsd has configured, including stopped ones

Re-exports. Exported here, defined elsewhere.

ExportDefined in
Listenerlisteners::Listener
{Claim,DatasetClaim,Disagreement,Estate,JailLayout,Reconciliation}reconcile::{Claim,DatasetClaim,Disagreement,Estate,JailLayout,Reconciliation}
{ConfiguredJails,Jail}jails::{ConfiguredJails,Jail}
{Dataset,Snapshot}datasets::{Dataset,Snapshot}

Boundary

Depends on no other workspace tier.

Shares tier operations with 42 other crates: operations-approval-workflow, operations-assessments, operations-block-imaging, operations-boot-media, operations-browser-agent-worker, operations-camera-discovery, operations-camera-liveview, operations-camera-registry, … (42 total).

_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._

Where it sits

Tier (ontology)operations
Architectural role (taxonomy)infrastructure adapter
Locationcrates/operations/jail-inventory
Vocabulary in force (lexicon)current

Dependencies

Runtime, in this workspace.

CrateTierOptionalOnly on
`operations-host-inventory`operationsnoalways

Runtime, from outside the workspace.

CrateRequirementFeaturesOptionalOnly on
serde^1derivenoalways
thiserror^2noalways

Development, from outside the workspace.

CrateRequirementFeaturesOptionalOnly on
serde_json^1noalways

Build. None.

Depended on by. Nothing in this workspace.

Signal flow — what reaches this crate, and what it reaches.

flowchart LR
  SELF["operations-jail-inventory"]
  SELF -->|runtime| n_operations_host_inventory["operations-host-inventory"]
  classDef self fill:#1f883d,stroke:#1f883d,color:#fff;
  class SELF self;

Feature flags

No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.

Targets

KindNameSource
exampleestate-report`examples/estate-report.rs`
liboperations_jail_inventory`src/lib.rs`
testestate`tests/estate.rs`

Error model

Error typeNamed by
EstateErrorResult

Operational characteristics

PropertyEvidence
async public surfacenone detected
async runtimenone detected
database accessnone detected
network I/Onone detected
unsafe codenone detected
environment variablesnone detected

No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.

Configuration

No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.

No workspace crate depends on this one.

Verification

KindCount
Unit tests0
Integration tests24
Examples1
Doctests0

Evidence by module. How often each public module is named by something executable.

ModuleTestsExamplesConsumers
crate root320
datasets620
jails640
listeners310
reconcile750
testkit510

What the tests establish, by name:

Documentation coverage

MeasureDocumentedTotal
Public items with rustdoc3637
Public modules with a //! block55
pie showData
    title Public items with rustdoc
    "Documented" : 36
    "No rustdoc detected" : 1

Metrics

MetricValue
Rust source files6
Source lines808
Code lines501
Public API items37
Public modules5
Tests24
Examples1
Cargo features0
Direct runtime dependencies3
Workspace reverse dependencies0
pie showData
    title Public API by kind
    "constant" : 7
    "enum" : 3
    "function" : 10
    "method" : 7
    "struct" : 9
    "type alias" : 1
pie showData
    title Rust source composition
    "Code" : 501
    "Blank or comment" : 307

Generation

Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.

Todas las operations · Manual