microsandbox_types/guest_flush.rs
1//! Optional guest filesystem writeback policy, independent of required storage barriers.
2
3use serde::{Deserialize, Serialize};
4
5//--------------------------------------------------------------------------------------------------
6// Types
7//--------------------------------------------------------------------------------------------------
8
9/// Guest filesystem writeback requested before capture or resident pause.
10///
11/// This policy controls root and captured block-filesystem writeback, including owned disks.
12/// It never disables host-backed directory synchronization, host I/O draining, or snapshot
13/// durability. Filesystem writeback does not flush application-owned buffers or commit
14/// application transactions.
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
16#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
17#[serde(rename_all = "lowercase")]
18pub enum GuestFlush {
19 /// Require writeback for live disk-only capture; no extra writeback for full capture,
20 /// branching, or pause. Stopped disk capture does not establish a guest flush.
21 #[default]
22 Auto,
23 /// Require acknowledged writeback before proceeding. A paused source must already
24 /// hold a matching flush boundary; a stopped source cannot satisfy this request.
25 Required,
26 /// Skip optional writeback, retaining all mandatory storage barriers.
27 Skip,
28}
29
30//--------------------------------------------------------------------------------------------------
31// Methods
32//--------------------------------------------------------------------------------------------------
33
34impl GuestFlush {
35 /// Whether this policy requires optional writeback for a live source.
36 ///
37 /// Callers must separately enforce mandatory storage barriers and distinguish stopped
38 /// capture from live disk capture. This predicate is not evidence that flushing occurred.
39 pub fn requires_writeback(self, disk_only: bool) -> bool {
40 match self {
41 Self::Auto => disk_only,
42 Self::Required => true,
43 Self::Skip => false,
44 }
45 }
46}
47
48//--------------------------------------------------------------------------------------------------
49// Trait Implementations
50//--------------------------------------------------------------------------------------------------
51
52impl std::str::FromStr for GuestFlush {
53 type Err = &'static str;
54
55 fn from_str(value: &str) -> Result<Self, Self::Err> {
56 match value {
57 "auto" => Ok(Self::Auto),
58 "required" => Ok(Self::Required),
59 "skip" => Ok(Self::Skip),
60 _ => Err("guest flush must be auto, required, or skip"),
61 }
62 }
63}
64
65//--------------------------------------------------------------------------------------------------
66// Tests
67//--------------------------------------------------------------------------------------------------
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn live_defaults_depend_on_capture_scope() {
75 for (policy, disk, full_or_pause) in [
76 (GuestFlush::Auto, true, false),
77 (GuestFlush::Required, true, true),
78 (GuestFlush::Skip, false, false),
79 ] {
80 assert_eq!(policy.requires_writeback(true), disk);
81 assert_eq!(policy.requires_writeback(false), full_or_pause);
82 }
83 }
84
85 #[test]
86 fn wire_policy_is_explicit_and_closed() {
87 assert_eq!(GuestFlush::default(), GuestFlush::Auto);
88 for (policy, wire) in [
89 (GuestFlush::Auto, "\"auto\""),
90 (GuestFlush::Required, "\"required\""),
91 (GuestFlush::Skip, "\"skip\""),
92 ] {
93 assert_eq!(serde_json::to_string(&policy).unwrap(), wire);
94 assert_eq!(serde_json::from_str::<GuestFlush>(wire).unwrap(), policy);
95 }
96 for invalid in ["true", "false", "null", "\"always\"", "\"off\""] {
97 assert!(serde_json::from_str::<GuestFlush>(invalid).is_err());
98 }
99 }
100}