Skip to main content

Crate scuffle

Crate scuffle 

Source
Expand description

§scuffle

scuffle provides an illumos-only wrapper around libscf, focused on interactions with property groups and properties of existing services, instances, and snapshots. Understanding SMF Properties is highly recommended background reading.

There are large swathes of libscf that are currently not available via scuffle due to its focus on properties, including:

  • The ability to create or delete services
  • Interacting with running instances or services in any way other than refreshing instances
  • All functionality related to snaplevels
  • All functionality related to templates

§Examples

Reading all properties from a specific property group in a service instance’s running snapshot:

fn read_from_snapshot(
    service_name: &str,
    instance_name: &str,
    property_group_name: &str,
) -> anyhow::Result<BTreeMap<String, Vec<Value>>> {
    // Get a handle to scf and the local scope.
    let scf = Scf::connect_current_zone()?;
    let scope = scf.scope_local()?;

    // Look up the property group within our snapshot by stepping through
    // each level.
    let Some(service) = scope.service(service_name)? else {
        bail!("service {service_name} not found");
    };
    let Some(instance) = service.instance(instance_name)? else {
        bail!("instance {instance_name} not found within {}", service.fmri());
    };
    let Some(snapshot) = instance.snapshot("running")? else {
        bail!("no running snapshot found for {}", instance.fmri());
    };
    let Some(pg) = snapshot.property_group_composed(property_group_name)? else {
        bail!(
            "property group {property_group_name} not found for {}",
            instance.fmri(),
        );
    };

    let mut all_properties = BTreeMap::new();
    for property in pg.properties()? {
        let property = property?;
        let values = property.values()?.collect::<Result<_, _>>()?;
        all_properties.insert(property.name().to_string(), values);
    }
    Ok(all_properties)
}

Adding a new property to an existing property group of a service instance:

fn add_new_property(
    service_name: &str,
    instance_name: &str,
    property_group_name: &str,
    property_name: &str,
    value: ValueRef<'_>,
) -> anyhow::Result<()> {
    // Get a handle to scf and the local scope.
    let scf = Scf::connect_current_zone()?;
    let scope = scf.scope_local()?;

    // Look up the property group within our instance by stepping through
    // each level.
    let Some(service) = scope.service(service_name)? else {
        bail!("service {service_name} not found");
    };
    let Some(instance) = service.instance(instance_name)? else {
        bail!("instance {instance_name} not found within {}", service.fmri());
    };
    let Some(mut pg) = instance.property_group_direct(property_group_name)? else {
        bail!(
            "property group {property_group_name} not found for {}",
            instance.fmri(),
        );
    };

    // Open a transaction on the property group.
    let tx = pg.transaction()?;

    // Start the transaction. This takes a snapshot of the property group's
    // current version; if it's modified by someone else between this point
    // and our attempt to `commit()` below, we'll get an `OutOfDate` result.
    let mut tx = tx.start()?;

    // Add an entry to the transaction to create the new property.
    tx.property_new(property_name, value)?;

    // Commit the transaction.
    match tx.commit()? {
        TransactionCommitResult::Success(_committed_tx) => Ok(()),
        TransactionCommitResult::OutOfDate(_reset_tx) => {
            // We'll return an error for this example, but real code may
            // want to call `pg.update()` and try again.
            bail!("property group concurrently modified");
        }
    }
}

See the examples/ directory for more complete examples.

§Error types

scuffle’s errors aim to provide extensive context; e.g., an error that occurs while operating on an instance will include that instance’s FMRI. These error types make extensive use of source errors as discussed in Defining Error Types and Logging Errors. It is critical that printing or logging of these error types walk the entire error chain as discussed in that document, or the underlying error(s) will not be emitted.

The examples above use anyhow to allow easy ?-propagation despite the multiple errors involved. scuffle does not currently provide a catch-all error type of its own.

§Features

scuffle has three optional Cargo features:

  • Enabling the daft feature adds implementations of daft::Diffable to Value, ValueRef, and ValueKind.
  • Enabling the smf-by-instance feature adds several Instance::smf_* methods for controlling the SMF state of an instance, but requires a libscf that includes recently-stabilized APIs.
  • Enabling the testing feature adds types to support writing tests that interact with SMF without needing to modify system-level SMF services / instances / properties; see “Testing Support” below.

§Stability

scuffle makes use of some non-public interfaces. Specifically:

  • Scf::connect_zone() uses an undocumented SCF handle decoration to connect to svc.configd inside the specified zone. This matches how svcadm and svcprop implement their -z zone flags.
  • If the smf-by-instance Cargo feature is not enabled, Instance::smf_refresh() uses a non-public function defined by libscf_priv.h (_smf_refresh_instance_i()).
  • If the testing feature is enabled, it uses several other non-public interfaces; see “Testing Support” below.

§Testing Support

If the testing feature is enabled, scuffle exports an isolated::IsolatedConfigd type that can run an instance of svc.configd inside a temporary directory. After creating an instance of this type, tests can connect to it via Scf::connect_isolated(), and then freely read and write properties within that instance without touching the real system’s svc.configd and without the permissions normally required to do that.

This comes with several caveats:

  • IsolatedConfigd takes advantage of undocumented and uncommitted interfaces in at least svccfg, svc.configd, and libscf itself. These may break in the future.
  • Refreshing instances inside an IsolatedConfigd via libscf does not work. scuffle works around this by refreshing instances via svccfg pointed at the isolated svc.configd, but this is a divergence from what production code will do for refresh.
  • Non-persistent property groups do not work inside IsolatedConfigd.

scuffle uses IsolatedConfigd for its own tests, and therefore does not have test coverage on features that interact with these restrictions.

Modules§

error
Error types used throughout scuffle.
isolated
Module for running an isolated svc.configd instance.

Structs§

Instance
Handle to an SMF instance.
Instances
Iterator over all Instances in a Service.
Properties
Iterator over all Propertys in a PropertyGroup.
Property
Handle to an SMF property within a property group.
PropertyGroup
Handle to an SMF property group.
PropertyGroups
Iterator over all PropertyGroups in an instance, service, or composed view (instance or snapshot).
Scf
Entry-point handle to libscf.
Scope
Handle to an SMF scope.
Service
Handle to an SMF service.
SmfMaintainFlags
Optional flags for putting an SMF instance into maintenance.
Snapshot
Handle to an SMF snapshot.
Snapshots
Iterator over all Snapshots in an Instance.
Transaction
Transaction for modifying properties within a PropertyGroup.
ValueDisplaySmf
Display adapter for Value and ValueRef.
Values
Iterator over all values in a Property.

Enums§

AddPropertyGroupFlags
Flags controlling creation of new property groups.
DeletePropertyGroupResult
Result of deleting a property group.
PropertyGroupComposed
Type-state marker for a PropertyGroup that is from a composed view.
PropertyGroupDirect
Type-state marker for a PropertyGroup that is directly attached to an Instance or Service.
PropertyGroupType
Property group types.
PropertyGroupUpdateResult
Result of updating a property group against its latest version.
SmfDegradeFlags
Optional flags for putting an SMF instance into the degraded state.
SmfEnableDisableFlags
Optional flags for enabling or disabling SMF instances.
TransactionCommitResult
Result of committing a Transaction.
TransactionCommitted
Type-state marker for a Transaction in the committed state.
TransactionReset
Type-state marker for a Transaction in the reset (initial) state.
TransactionStarted
Type-state marker for a Transaction in the started state.
Value
Owned representation of a libscf value.
ValueKind
Enum with a one-to-one mapping to scf_type_t.
ValueRef
Analogue of Value that contains borrowed data.

Traits§

EditPropertyGroups
Trait to add property groups to a Service or Instance.
HasComposedPropertyGroups
Trait to look up composed property groups from an Instance or Snapshot.
HasDirectPropertyGroups
Trait to look up direct-attached property groups from a Service or Instance.