1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// We have rustdoc links to types and methods that don't always exist; don't
// warn about those broken links. Docs should be built with `--all-features` in
// general.
//! # scuffle
//!
//! `scuffle` provides an illumos-only wrapper around [`libscf`][manlibscf],
//! focused on interactions with property groups and properties of existing
//! services, instances, and snapshots. [Understanding SMF Properties][blog] 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 `snaplevel`s
//! * All functionality related to templates
//!
//! # Examples
//!
//! Reading all properties from a specific property group in a service
//! instance's `running` snapshot:
//!
//! ```no_run
//! # use scuffle::{HasComposedPropertyGroups, Scf, Value};
//! # use std::collections::BTreeMap;
//! # use anyhow::bail;
//! 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:
//!
//! ```no_run
//! # use scuffle::HasDirectPropertyGroups;
//! # use scuffle::{Scf, TransactionCommitResult, ValueRef};
//! # use anyhow::bail;
//! 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][error-doc]. 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.
//!
//! [error-doc]:
//! <https://github.com/oxidecomputer/omicron/blob/main/docs/error-types-and-logging.adoc>
//!
//! # Features
//!
//! `scuffle` has three optional Cargo features:
//!
//! * Enabling the `daft` feature adds implementations of
//! [`daft::Diffable`][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](https://www.illumos.org/issues/18043).
//! * 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.
//!
//! [manlibscf]: https://smartos.org/man/3LIB/libscf
//! [blog]: https://www.davepacheco.net/blog/2026/smf-properties/
//! [diffable]: https://docs.rs/daft/latest/daft/trait.Diffable.html
// TODO-cleanup Should we (eventually?) remove this private interface, remove
// the `smf-by-instance` feature, and require running on a new-enough illumos
// that provides the `smf_OPERATION_by_instance()` functions?
pub use AddPropertyGroupFlags;
pub use DeletePropertyGroupResult;
pub use EditPropertyGroups;
pub use HasComposedPropertyGroups;
pub use HasDirectPropertyGroups;
pub use Instance;
pub use Instances;
pub use Properties;
pub use Property;
pub use PropertyGroup;
pub use PropertyGroupComposed;
pub use PropertyGroupDirect;
pub use PropertyGroupType;
pub use PropertyGroupUpdateResult;
pub use PropertyGroups;
pub use Scf;
pub use Scope;
pub use Service;
pub use Snapshot;
pub use Snapshots;
pub use Transaction;
pub use TransactionCommitResult;
pub use TransactionCommitted;
pub use TransactionReset;
pub use TransactionStarted;
pub use Value;
pub use ValueDisplaySmf;
pub use ValueKind;
pub use ValueRef;
pub use Values;
pub use *;