oci_spec/runtime/
state.rs

1use crate::error::OciSpecError;
2
3use std::{
4    fs,
5    io::{BufReader, BufWriter, Write},
6    path::{Path, PathBuf},
7};
8
9use derive_builder::Builder;
10use getset::{Getters, MutGetters, Setters};
11use serde::{Deserialize, Serialize};
12use std::{collections::HashMap, fmt::Display};
13
14/// ContainerState represents the state of a container.
15#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Deserialize, Serialize)]
16#[serde(rename_all = "lowercase")]
17pub enum ContainerState {
18    /// Creating indicates that the container is being created,
19    Creating,
20
21    /// Created indicates that the runtime has finished the create operation,
22    /// but the container exists but has not been run yet.
23    Created,
24
25    /// Running indicates that the container process has executed the
26    /// user-specified program but has not exited
27    Running,
28
29    /// Stopped indicates that the container process has exited,
30    /// and does not have a created or running process.
31    #[default]
32    Stopped,
33}
34
35impl Display for ContainerState {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match self {
38            ContainerState::Creating => write!(f, "creating"),
39            ContainerState::Created => write!(f, "created"),
40            ContainerState::Running => write!(f, "running"),
41            ContainerState::Stopped => write!(f, "stopped"),
42        }
43    }
44}
45
46/// State holds information about the runtime state of the container.
47#[derive(
48    Builder,
49    Clone,
50    Debug,
51    Default,
52    Deserialize,
53    Eq,
54    Getters,
55    MutGetters,
56    Setters,
57    PartialEq,
58    Serialize,
59)]
60#[serde(rename_all = "camelCase")]
61#[builder(
62    default,
63    pattern = "owned",
64    setter(into, strip_option),
65    build_fn(error = "OciSpecError")
66)]
67#[getset(get_mut = "pub", get = "pub", set = "pub")]
68pub struct State {
69    /// version is the version of the specification that is supported.
70    #[serde(default, rename = "ociVersion")]
71    version: String,
72
73    /// id is the container ID
74    #[serde(default)]
75    id: String,
76
77    /// status is the runtime status of the container.
78    #[serde(default)]
79    status: ContainerState,
80
81    /// pid is the process ID for the container process.
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pid: Option<i32>,
84
85    /// bundle is the path to the container's bundle directory.
86    #[serde(default)]
87    bundle: PathBuf,
88
89    /// annotations are key values associated with the container.
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    annotations: Option<HashMap<String, String>>,
92}
93
94impl State {
95    /// Load a State from the provided JSON file path.
96    /// # Errors
97    /// This function will return an [OciSpecError::Io] if the file does not exist or an
98    /// [OciSpecError::SerDe] if the JSON is invalid.
99    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, OciSpecError> {
100        let path = path.as_ref();
101        let file = fs::File::open(path)?;
102        let reader = BufReader::new(file);
103        let state = serde_json::from_reader(reader)?;
104        Ok(state)
105    }
106
107    /// Save a State to the provided JSON file path.
108    /// # Errors
109    /// This function will return an [OciSpecError::Io] if a file cannot be created at the provided
110    /// path or an [OciSpecError::SerDe] if the state cannot be serialized.
111    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), OciSpecError> {
112        let path = path.as_ref();
113        let file = fs::File::create(path)?;
114        let mut writer = BufWriter::new(file);
115        serde_json::to_writer(&mut writer, self)?;
116        writer.flush()?;
117        Ok(())
118    }
119}
120
121/// SeccompFdName is the name of the seccomp notify file descriptor.
122/// Used in ContainerProcessState.fds to identify seccomp listener file descriptors.
123/// See: <https://github.com/opencontainers/runtime-spec/blob/main/specs-go/state.go>
124pub const SECCOMP_FD_NAME: &str = "seccompFd";
125
126/// ContainerProcessState holds information about the state of a container process.
127#[derive(
128    Builder,
129    Clone,
130    Debug,
131    Default,
132    Deserialize,
133    Eq,
134    Getters,
135    MutGetters,
136    Setters,
137    PartialEq,
138    Serialize,
139)]
140#[serde(rename_all = "camelCase")]
141#[builder(
142    default,
143    pattern = "owned",
144    setter(into, strip_option),
145    build_fn(error = "OciSpecError")
146)]
147#[getset(get_mut = "pub", get = "pub", set = "pub")]
148pub struct ContainerProcessState {
149    /// version is the version of the specification that is supported.
150    #[serde(default, rename = "ociVersion")]
151    version: String,
152
153    /// fds is a string array containing the names of the file descriptors passed.
154    /// The index of the name in this array corresponds to index of the file
155    /// descriptor in the `SCM_RIGHTS` array.
156    #[serde(default)]
157    fds: Vec<String>,
158
159    /// pid is the process ID as seen by the runtime.
160    #[serde(default)]
161    pid: i32,
162
163    /// opaque metadata.
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    metadata: Option<String>,
166
167    /// state of the container.
168    #[serde(default)]
169    state: State,
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_load_save() {
178        let state = State {
179            ..Default::default()
180        };
181        let test_dir = tempfile::tempdir().expect("failed to create tmp test dir");
182        let state_path = test_dir.keep().join("state.json");
183
184        // Test first save the default config, and then load the saved config.
185        // The before and after should be the same.
186        state.save(&state_path).expect("failed to save state");
187        let loaded_state = State::load(&state_path).expect("failed to load state");
188        assert_eq!(
189            state, loaded_state,
190            "The saved state is not the same as the loaded state"
191        );
192    }
193}