composefs_oci/varlink_types.rs
1//! Shared wire types and the `OciProxy` client trait for the
2//! `org.composefs.Oci` varlink interface.
3//!
4//! These types are defined here (in `composefs-oci`) rather than
5//! `composefs-ctl` so that both the repo-side service (`CfsctlService` in
6//! composefs-ctl) and the containers-storage service (`CstorLayerService` in
7//! composefs-storage) can share the same proxy trait without creating a
8//! dependency cycle.
9//!
10//! `composefs-ctl` re-exports everything from this module; callers should
11//! prefer `composefs_oci::varlink_types` or the re-exports in
12//! `composefs_ctl::varlink::{layer_sync,oci::OciError,proxy::OciProxy}`.
13//!
14//! # Feature gate
15//!
16//! This module is compiled only when the `varlink` feature is enabled on
17//! `composefs-oci` (which pulls in `zlink`).
18
19#![allow(missing_docs)]
20
21use serde::{Deserialize, Serialize};
22
23// ── Locator for a layer inside containers-storage ────────────────────────────
24
25/// Locator for a layer that lives inside a containers-storage store.
26///
27/// Both fields are required when routing a `GetLayer` call to the
28/// `CstorLayerService`; the repo-side service ignores this field entirely.
29#[derive(Debug, Clone, Serialize, Deserialize, zlink::introspect::Type)]
30pub struct StorageLocator {
31 /// Absolute path to the containers-storage root directory
32 /// (e.g. `/var/lib/containers/storage`).
33 pub storage_path: String,
34 /// The layer ID within that storage root, as returned by
35 /// `storage_layer_ids()`.
36 pub layer_id: String,
37}
38
39/// Parameters for the `GetLayer` method of the `org.composefs.Oci` interface.
40///
41/// Exactly one of `diff_id` or `storage` must be set:
42/// - **Repo service** (`CfsctlService`): reads `diff_id`, errors if `None`.
43/// - **Cstor service** (`CstorLayerService`): reads `storage`, errors if `None`.
44///
45/// Both fields are `Option` for forward-compatibility: new locator kinds can
46/// be added in future without breaking old clients.
47#[derive(Debug, Clone, Default, Serialize, Deserialize, zlink::introspect::Type)]
48pub struct GetLayerParams {
49 /// OCI diff-id (`sha256:…`) identifying the layer in a composefs repo.
50 pub diff_id: Option<String>,
51 /// Location of a specific layer inside a containers-storage store.
52 pub storage: Option<StorageLocator>,
53 /// Whether the consumer of this layer's bytes can bypass file DAC
54 /// permissions (real root or `CAP_DAC_OVERRIDE`). When `true`, the cstor
55 /// service may emit `FileBackedData` chunks even for non-world-readable
56 /// files, since the consumer can open them directly. Defaults to `false`
57 /// for backward compatibility.
58 #[serde(default)]
59 pub consumer_has_cap_dac_override: bool,
60}
61
62// ── Reply types ───────────────────────────────────────────────────────────────
63
64/// Reply from `GetInfo`: capability tokens supported by this service.
65#[derive(Debug, Clone, Serialize, Deserialize, zlink::introspect::Type)]
66pub struct GetInfoReply {
67 /// Capability tokens advertised by this service instance.
68 ///
69 /// Currently only `"splitdirfdstream-v0"` is defined. The cstor service
70 /// additionally reports `"source-containers-storage"` and `"read-only"`.
71 pub features: Vec<String>,
72}
73
74/// Reply from `HasLayer`: whether the layer is present in the repository.
75#[derive(Debug, Clone, Serialize, Deserialize, zlink::introspect::Type)]
76pub struct HasLayerReply {
77 /// Whether the layer splitstream for the given diff-id is present.
78 pub present: bool,
79 /// Hex-encoded fs-verity hash of the layer splitstream, if present.
80 pub layer_verity: Option<String>,
81}
82
83/// Reply from `GetLayer`: the number of diff-directory slots in the logical FD
84/// array.
85///
86/// `GetLayer` is a **streaming** method (`more`): it yields multiple frames,
87/// each carrying a batch of FDs. The client MUST concatenate the FD batches
88/// from all frames (in arrival order) to reconstruct the full logical FD array:
89///
90/// - `fds[0]` — data pipe read end (carries the `splitdirfdstream` bytes).
91/// - `fds[1..=dir_count]` — the dirfds region (`dir_count` slots total). The
92/// real objects-directory fd sits at a sparse, hash-determined index within
93/// this region; the remaining (gap) slots hold inert dummy fds that
94/// `reconstruct` never dereferences.
95/// - `fds[dir_count+1..]` — opaque lifetime FDs. The client MUST hold every
96/// one of these open until it has finished reading and processing all dir
97/// fds, then close them all to signal completion to the server.
98#[derive(Debug, Clone, Serialize, Deserialize, zlink::introspect::Type)]
99pub struct GetLayerReply {
100 /// Number of diff-directory file descriptors in the full logical FD array
101 /// (i.e. `fds[1..=dir_count]` after concatenating all frames' batches).
102 pub dir_count: u32,
103}
104
105/// Reply from `PutLayer`: the verity hash of the imported layer, whether
106/// it was already present, and per-object transfer statistics.
107#[derive(Debug, Clone, Serialize, Deserialize, zlink::introspect::Type)]
108pub struct PutLayerReply {
109 /// Hex-encoded fs-verity hash of the committed layer splitstream.
110 pub layer_verity: String,
111 /// `true` if the layer was already present before this call.
112 pub already_present: bool,
113
114 /// Number of objects that were reflinked (FICLONE) into the destination.
115 #[serde(default)]
116 pub objects_reflinked: u64,
117 /// Number of objects hardlinked into the destination.
118 #[serde(default)]
119 pub objects_hardlinked: u64,
120 /// Number of objects byte-copied into the destination.
121 #[serde(default)]
122 pub objects_copied: u64,
123 /// Number of objects already present in the destination (skipped).
124 #[serde(default)]
125 pub objects_already_present: u64,
126}
127
128/// A single (diff_id, layer_verity) pair passed to `FinalizeImage`.
129#[derive(Debug, Clone, Serialize, Deserialize, zlink::introspect::Type)]
130pub struct LayerRef {
131 /// OCI diff-id of the layer (e.g. `"sha256:abcd..."`).
132 pub diff_id: String,
133 /// Hex-encoded fs-verity hash of the layer splitstream in the destination
134 /// repository, as returned by `PutLayer`.
135 pub layer_verity: String,
136}
137
138/// Reply from `FinalizeImage`.
139#[derive(Debug, Clone, Serialize, Deserialize, zlink::introspect::Type)]
140pub struct FinalizeImageReply {
141 /// OCI digest of the manifest.
142 pub manifest_digest: String,
143 /// Hex-encoded fs-verity hash of the manifest splitstream.
144 pub manifest_verity: String,
145 /// OCI digest of the config.
146 pub config_digest: String,
147 /// Hex-encoded fs-verity hash of the config splitstream.
148 pub config_verity: String,
149}
150
151// ── OciError ──────────────────────────────────────────────────────────────────
152
153/// Errors returned by the `org.composefs.Oci` interface.
154#[derive(Debug, zlink::ReplyError, zlink::introspect::ReplyError)]
155#[zlink(interface = "org.composefs.Oci")]
156pub enum OciError {
157 /// The repository could not be found or opened at the configured path.
158 RepoNotFound {
159 /// Description of the failure.
160 message: String,
161 },
162 /// The given handle does not refer to an open repository.
163 InvalidHandle {
164 /// The handle that was not found.
165 handle: u64,
166 },
167 /// The named OCI image/reference does not exist.
168 NoSuchImage {
169 /// The image reference that was not found.
170 image: String,
171 },
172 /// An unexpected internal error occurred while servicing the request.
173 InternalError {
174 /// Description of the failure.
175 message: String,
176 },
177 /// The requested layer (by diff-id) is not present in the repository.
178 NoSuchLayer {
179 /// The diff-id that was not found.
180 diff_id: String,
181 },
182 /// A supplied digest/diff-id string was malformed.
183 InvalidDigest {
184 /// Human-readable description of the parse failure.
185 message: String,
186 },
187 /// Received layer content did not hash to the declared diff-id.
188 DiffIdMismatch {
189 /// The diff_id that was declared by the client.
190 expected: String,
191 /// The sha256 digest of the data that was actually received.
192 actual: String,
193 },
194 /// The request was malformed (e.g. wrong fd count).
195 InvalidRequest {
196 /// Human-readable description of what was wrong.
197 message: String,
198 },
199 /// The total fd count exceeds the per-frame cap for a `more=false` call.
200 FdLimitExceeded {
201 /// Total number of fds that would be sent.
202 fd_count: u64,
203 /// The per-frame cap that was exceeded.
204 max_per_frame: u64,
205 },
206}
207
208// ── OciProxy trait ────────────────────────────────────────────────────────────
209
210/// Typed client proxy for the `org.composefs.Oci` varlink interface.
211///
212/// Both the composefs repo service and the containers-storage service expose
213/// this interface; this proxy trait can be used against either.
214#[zlink::proxy(interface = "org.composefs.Oci")]
215pub trait OciProxy {
216 /// Query capability tokens supported by the service.
217 async fn get_info(&mut self) -> zlink::Result<Result<GetInfoReply, OciError>>;
218
219 /// Check whether a layer is present in the repository.
220 async fn has_layer(
221 &mut self,
222 handle: u64,
223 diff_id: &str,
224 ) -> zlink::Result<Result<HasLayerReply, OciError>>;
225
226 /// Stream the layer as a `splitdirfdstream` with full hardened fd-transport
227 /// contract (sparse dirfds, keepalive, lifetime fds, multi-frame).
228 ///
229 /// `params.diff_id` is used by the repo service; `params.storage` is used
230 /// by the cstor service.
231 #[zlink(more, return_fds)]
232 async fn get_layer(
233 &mut self,
234 handle: u64,
235 params: GetLayerParams,
236 ) -> zlink::Result<
237 impl zlink::futures_util::Stream<
238 Item = zlink::Result<(Result<GetLayerReply, OciError>, Vec<std::os::fd::OwnedFd>)>,
239 >,
240 >;
241
242 /// Receive a layer as a `splitdirfdstream` from the client and import it.
243 async fn put_layer(
244 &mut self,
245 handle: u64,
246 diff_id: &str,
247 zerocopy: bool,
248 #[zlink(fds)] fds: Vec<std::os::fd::OwnedFd>,
249 ) -> zlink::Result<Result<PutLayerReply, OciError>>;
250
251 /// Finalize an OCI image after all layers have been imported.
252 async fn finalize_image(
253 &mut self,
254 handle: u64,
255 manifest_json: &str,
256 config_json: &str,
257 layers: Vec<LayerRef>,
258 name: Option<&str>,
259 ) -> zlink::Result<Result<FinalizeImageReply, OciError>>;
260}