1use std::collections::HashMap;
3use std::fmt::Display;
4use std::fs;
5use std::fs::File;
6use std::io::{BufReader, BufWriter, Write};
7use std::path::{Path, PathBuf};
8
9use chrono::{DateTime, Utc};
10use oci_spec::OciSpecError;
11use oci_spec::runtime::{
12 ContainerState as OciContainerState, State as OciState, StateBuilder as OciStateBuilder,
13 VERSION as OCI_RUNTIME_VERSION,
14};
15use serde::{Deserialize, Serialize};
16use tracing::instrument;
17
18#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Default)]
20#[serde(rename_all = "camelCase")]
21pub enum ContainerStatus {
22 #[default]
24 Creating,
25 Created,
27 Running,
29 Stopped,
31 Paused,
33}
34
35impl ContainerStatus {
36 pub fn can_start(&self) -> bool {
37 matches!(self, ContainerStatus::Created)
38 }
39
40 pub fn can_kill(&self) -> bool {
41 use ContainerStatus::*;
42 match self {
43 Creating | Stopped => false,
44 Created | Running | Paused => true,
45 }
46 }
47
48 pub fn can_delete(&self) -> bool {
49 matches!(self, ContainerStatus::Stopped)
50 }
51
52 pub fn can_pause(&self) -> bool {
53 matches!(self, ContainerStatus::Running)
54 }
55
56 pub fn can_resume(&self) -> bool {
57 matches!(self, ContainerStatus::Paused)
58 }
59}
60
61impl Display for ContainerStatus {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 let print = match *self {
64 Self::Creating => "Creating",
65 Self::Created => "Created",
66 Self::Running => "Running",
67 Self::Stopped => "Stopped",
68 Self::Paused => "Paused",
69 };
70
71 write!(f, "{print}")
72 }
73}
74
75#[derive(Debug, thiserror::Error)]
76pub enum StateError {
77 #[error("failed to open container state file {state_file_path:?}")]
78 OpenStateFile {
79 state_file_path: PathBuf,
80 source: std::io::Error,
81 },
82 #[error("failed to parse container state file {state_file_path:?}")]
83 ParseStateFile {
84 state_file_path: PathBuf,
85 source: serde_json::Error,
86 },
87 #[error("failed to write container state file {state_file_path:?}")]
88 WriteStateFile {
89 state_file_path: PathBuf,
90 source: std::io::Error,
91 },
92}
93
94type Result<T> = std::result::Result<T, StateError>;
95
96#[derive(Serialize, Deserialize, Debug, Clone, Default)]
98#[serde(rename_all = "camelCase")]
99pub struct State {
100 pub oci_version: String,
102 pub id: String,
104 pub status: ContainerStatus,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub pid: Option<i32>,
109 pub bundle: PathBuf,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub annotations: Option<HashMap<String, String>>,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub created: Option<DateTime<Utc>>,
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub creator: Option<u32>,
120 pub use_systemd: bool,
122 pub clean_up_intel_rdt_subdirectory: Option<bool>,
124}
125
126impl State {
127 const STATE_FILE_PATH: &'static str = "state.json";
128
129 pub fn new(
130 container_id: &str,
131 status: ContainerStatus,
132 pid: Option<i32>,
133 bundle: PathBuf,
134 ) -> Self {
135 Self {
136 oci_version: OCI_RUNTIME_VERSION.to_string(),
137 id: container_id.to_string(),
138 status,
139 pid,
140 bundle,
141 annotations: Some(HashMap::default()),
142 created: None,
143 creator: None,
144 use_systemd: false,
145 clean_up_intel_rdt_subdirectory: None,
146 }
147 }
148
149 #[instrument(level = "trace")]
150 pub fn save(&self, container_root: &Path) -> Result<()> {
151 let state_file_path = Self::file_path(container_root);
152 let file = fs::OpenOptions::new()
153 .read(true)
154 .write(true)
155 .append(false)
156 .create(true)
157 .truncate(true)
158 .open(&state_file_path)
159 .map_err(|err| {
160 tracing::error!(
161 state_file_path = ?state_file_path,
162 err = %err,
163 "failed to open container state file",
164 );
165 StateError::OpenStateFile {
166 state_file_path: state_file_path.to_owned(),
167 source: err,
168 }
169 })?;
170 let mut writer = BufWriter::new(file);
171 serde_json::to_writer(&mut writer, self).map_err(|err| {
172 tracing::error!(
173 ?state_file_path,
174 %err,
175 "failed to parse container state file",
176 );
177 StateError::ParseStateFile {
178 state_file_path: state_file_path.to_owned(),
179 source: err,
180 }
181 })?;
182 writer.flush().map_err(|err| {
183 tracing::error!(
184 ?state_file_path,
185 %err,
186 "failed to write container state file",
187 );
188 StateError::WriteStateFile {
189 state_file_path: state_file_path.to_owned(),
190 source: err,
191 }
192 })?;
193
194 Ok(())
195 }
196
197 pub fn load(container_root: &Path) -> Result<Self> {
198 let state_file_path = Self::file_path(container_root);
199 let state_file = File::open(&state_file_path).map_err(|err| {
200 tracing::error!(
201 ?state_file_path,
202 %err,
203 "failed to open container state file",
204 );
205 StateError::OpenStateFile {
206 state_file_path: state_file_path.to_owned(),
207 source: err,
208 }
209 })?;
210
211 let state: Self = serde_json::from_reader(BufReader::new(state_file)).map_err(|err| {
212 tracing::error!(
213 ?state_file_path,
214 %err,
215 "failed to parse container state file",
216 );
217 StateError::ParseStateFile {
218 state_file_path: state_file_path.to_owned(),
219 source: err,
220 }
221 })?;
222
223 Ok(state)
224 }
225
226 pub fn file_path(container_root: &Path) -> PathBuf {
237 container_root.join(Self::STATE_FILE_PATH)
238 }
239}
240
241#[derive(Debug, thiserror::Error)]
243pub enum StateConversionError {
244 #[error("failed to build OCI state: {0}")]
245 OciStateBuild(#[from] OciSpecError),
246 #[error("invalid container status for OCI conversion: {0}")]
247 InvalidStatus(ContainerStatus),
248}
249
250impl TryFrom<&State> for OciState {
255 type Error = StateConversionError;
256
257 fn try_from(state: &State) -> std::result::Result<Self, Self::Error> {
258 let status = OciContainerState::try_from(state.status)?;
259
260 let mut builder = OciStateBuilder::default()
261 .version(state.oci_version.clone())
262 .id(state.id.clone())
263 .status(status)
264 .bundle(state.bundle.clone());
265
266 if let Some(annotations) = &state.annotations {
268 builder = builder.annotations(annotations.clone());
269 }
270 if let Some(pid) = state.pid {
271 builder = builder.pid(pid);
272 }
273
274 Ok(builder.build()?)
275 }
276}
277
278impl TryFrom<ContainerStatus> for OciContainerState {
279 type Error = StateConversionError;
280
281 fn try_from(status: ContainerStatus) -> std::result::Result<Self, Self::Error> {
282 match status {
283 ContainerStatus::Creating => Ok(OciContainerState::Creating),
284 ContainerStatus::Created => Ok(OciContainerState::Created),
285 ContainerStatus::Running => Ok(OciContainerState::Running),
286 ContainerStatus::Stopped => Ok(OciContainerState::Stopped),
287 ContainerStatus::Paused => Err(StateConversionError::InvalidStatus(status)),
289 }
290 }
291}
292
293#[deprecated(
295 since = "0.6.0",
296 note = "Use oci_spec::runtime::ContainerProcessState instead"
297)]
298#[derive(Serialize, Deserialize, Debug, Default)]
299#[serde(rename_all = "camelCase")]
300pub struct ContainerProcessState {
301 pub oci_version: String,
303 pub fds: Vec<String>,
307 pub pid: i32,
309 pub metadata: String,
311 pub state: State,
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn test_creating_status() {
321 let cstatus = ContainerStatus::default();
322 assert!(!cstatus.can_start());
323 assert!(!cstatus.can_delete());
324 assert!(!cstatus.can_kill());
325 assert!(!cstatus.can_pause());
326 assert!(!cstatus.can_resume());
327 }
328
329 #[test]
330 fn test_create_status() {
331 let cstatus = ContainerStatus::Created;
332 assert!(cstatus.can_start());
333 assert!(!cstatus.can_delete());
334 assert!(cstatus.can_kill());
335 assert!(!cstatus.can_pause());
336 assert!(!cstatus.can_resume());
337 }
338
339 #[test]
340 fn test_running_status() {
341 let cstatus = ContainerStatus::Running;
342 assert!(!cstatus.can_start());
343 assert!(!cstatus.can_delete());
344 assert!(cstatus.can_kill());
345 assert!(cstatus.can_pause());
346 assert!(!cstatus.can_resume());
347 }
348
349 #[test]
350 fn test_stopped_status() {
351 let cstatus = ContainerStatus::Stopped;
352 assert!(!cstatus.can_start());
353 assert!(cstatus.can_delete());
354 assert!(!cstatus.can_kill());
355 assert!(!cstatus.can_pause());
356 assert!(!cstatus.can_resume());
357 }
358
359 #[test]
360 fn test_paused_status() {
361 let cstatus = ContainerStatus::Paused;
362 assert!(!cstatus.can_start());
363 assert!(!cstatus.can_delete());
364 assert!(cstatus.can_kill());
365 assert!(!cstatus.can_pause());
366 assert!(cstatus.can_resume());
367 }
368}