Skip to main content

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}