Skip to main content

libcontainer/container/
state.rs

1//! Information about status and state of the container
2use 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/// Indicates status of the container
19#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Default)]
20#[serde(rename_all = "camelCase")]
21pub enum ContainerStatus {
22    // The container is being created
23    #[default]
24    Creating,
25    // The runtime has finished the create operation
26    Created,
27    // The container process has executed the user-specified program but has not exited
28    Running,
29    // The container process has exited
30    Stopped,
31    // The container process has paused
32    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/// Stores the state information of the container
97#[derive(Serialize, Deserialize, Debug, Clone, Default)]
98#[serde(rename_all = "camelCase")]
99pub struct State {
100    // Version is the version of the specification that is supported.
101    pub oci_version: String,
102    // ID is the container ID
103    pub id: String,
104    // Status is the runtime status of the container.
105    pub status: ContainerStatus,
106    // Pid is the process ID for the container process.
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub pid: Option<i32>,
109    // Bundle is the path to the container's bundle directory.
110    pub bundle: PathBuf,
111    // Annotations are key values associated with the container.
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub annotations: Option<HashMap<String, String>>,
114    // Creation time of the container
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub created: Option<DateTime<Utc>>,
117    // User that created the container
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub creator: Option<u32>,
120    // Specifies if systemd should be used to manage cgroups
121    pub use_systemd: bool,
122    // Specifies if the Intel RDT subdirectory needs be cleaned up.
123    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    /// Returns the path to the state JSON file for the provided `container_root`.
227    ///
228    /// ```
229    /// # use std::path::Path;
230    /// # use libcontainer::container::State;
231    ///
232    /// let container_root = Path::new("/var/run/containers/container");
233    /// let state_file = State::file_path(&container_root);
234    /// assert_eq!(state_file.to_str(), Some("/var/run/containers/container/state.json"));
235    /// ```
236    pub fn file_path(container_root: &Path) -> PathBuf {
237        container_root.join(Self::STATE_FILE_PATH)
238    }
239}
240
241/// Error type for state conversion failures.
242#[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
250/// Convert internal State to OCI-compliant State (by reference, cloning necessary fields).
251///
252/// Based on runc's implementation:
253/// https://github.com/opencontainers/runc/blob/v2.2.1/libcontainer/container_linux.go#L961
254impl 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        // Preserve None vs empty map distinction per OCI spec
267        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            // Paused is not defined in the OCI spec.
288            ContainerStatus::Paused => Err(StateConversionError::InvalidStatus(status)),
289        }
290    }
291}
292
293/// Deprecated: Use [`oci_spec::runtime::ContainerProcessState`] instead.
294#[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    // Version is the version of the specification that is supported.
302    pub oci_version: String,
303    // Fds is a string array containing the names of the file descriptors passed.
304    // The index of the name in this array corresponds to index of the file
305    // descriptor in the `SCM_RIGHTS` array.
306    pub fds: Vec<String>,
307    // Pid is the process ID as seen by the runtime.
308    pub pid: i32,
309    // Opaque metadata.
310    pub metadata: String,
311    // State of the container.
312    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}