1use std::fs;
2use std::io::{BufReader, BufWriter, Write};
3use std::path::{Path, PathBuf};
4
5use oci_spec::runtime::{Hooks, Spec};
6use serde::{Deserialize, Serialize};
7
8use crate::utils;
9
10pub enum PersonalityDomain {
11 Linux = 0x0000,
12 Linux32 = 0x0008,
13}
14
15#[derive(Debug, thiserror::Error)]
16pub enum ConfigError {
17 #[error("failed to save config")]
18 SaveIO {
19 source: std::io::Error,
20 path: PathBuf,
21 },
22 #[error("failed to save config")]
23 SaveEncode {
24 source: serde_json::Error,
25 path: PathBuf,
26 },
27 #[error("failed to parse config")]
28 LoadIO {
29 source: std::io::Error,
30 path: PathBuf,
31 },
32 #[error("failed to parse config")]
33 LoadParse {
34 source: serde_json::Error,
35 path: PathBuf,
36 },
37 #[error("missing linux in spec")]
38 MissingLinux,
39}
40
41type Result<T> = std::result::Result<T, ConfigError>;
42
43const YOUKI_CONFIG_NAME: &str = "youki_config.json";
44
45#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
48#[non_exhaustive]
49pub struct YoukiConfig {
50 pub hooks: Option<Hooks>,
51 pub cgroup_path: PathBuf,
52}
53
54impl YoukiConfig {
55 pub fn from_spec(spec: &Spec, container_id: &str) -> Result<Self> {
56 Ok(YoukiConfig {
57 hooks: spec.hooks().clone(),
58 cgroup_path: utils::get_cgroup_path(
59 spec.linux()
60 .as_ref()
61 .ok_or(ConfigError::MissingLinux)?
62 .cgroups_path(),
63 container_id,
64 ),
65 })
66 }
67
68 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
69 let file = fs::File::create(path.as_ref().join(YOUKI_CONFIG_NAME)).map_err(|err| {
70 ConfigError::SaveIO {
71 source: err,
72 path: path.as_ref().to_owned(),
73 }
74 })?;
75 let mut writer = BufWriter::new(file);
76 serde_json::to_writer(&mut writer, self).map_err(|err| ConfigError::SaveEncode {
77 source: err,
78 path: path.as_ref().to_owned(),
79 })?;
80 writer.flush().map_err(|err| ConfigError::SaveIO {
81 source: err,
82 path: path.as_ref().to_owned(),
83 })?;
84
85 Ok(())
86 }
87
88 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
89 let path = path.as_ref();
90 let file =
91 fs::File::open(path.join(YOUKI_CONFIG_NAME)).map_err(|err| ConfigError::LoadIO {
92 source: err,
93 path: path.to_owned(),
94 })?;
95 let reader = BufReader::new(file);
96 let config = serde_json::from_reader(reader).map_err(|err| ConfigError::LoadParse {
97 source: err,
98 path: path.to_owned(),
99 })?;
100 Ok(config)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use anyhow::Result;
107
108 use super::*;
109
110 #[test]
111 fn test_config_from_spec() -> Result<()> {
112 let container_id = "sample";
113 let spec = Spec::default();
114 let config = YoukiConfig::from_spec(&spec, container_id)?;
115 assert_eq!(&config.hooks, spec.hooks());
116 dbg!(&config.cgroup_path);
117 assert_eq!(
118 config.cgroup_path,
119 PathBuf::from(format!(":youki:{container_id}"))
120 );
121 Ok(())
122 }
123
124 #[test]
125 fn test_config_save_and_load() -> Result<()> {
126 let container_id = "sample";
127 let tmp = tempfile::tempdir().expect("create temp dir");
128 let spec = Spec::default();
129 let config = YoukiConfig::from_spec(&spec, container_id)?;
130 config.save(&tmp)?;
131 let act = YoukiConfig::load(&tmp)?;
132 assert_eq!(act, config);
133 Ok(())
134 }
135}