Browse Source

Merge pull request #68 from DragonOS-Community/longjin/add-rootfs-config

feat(dadk-config): 添加rootfs配置文件
LoGin 5 months ago
parent
commit
1ad837a449

+ 6 - 0
crates/test_base/src/dadk_config.rs

@@ -18,6 +18,12 @@ impl DadkConfigTestContext {
     pub fn abs_path(&self, relative_path: &str) -> PathBuf {
         self.test_base_path.join(relative_path)
     }
+
+    /// 获取 dadk配置模版的路径
+    pub fn templates_dir(&self) -> PathBuf {
+        const TEMPLATES_DIR: &str = "templates";
+        self.abs_path(TEMPLATES_DIR)
+    }
 }
 
 impl TestContext for DadkConfigTestContext {

+ 11 - 11
dadk-config/src/manifest.rs

@@ -10,7 +10,7 @@ use toml;
 
 /// The main configuration file for DADK
 #[derive(Debug, Clone, Deserialize)]
-pub struct DadkManifest {
+pub struct DadkManifestFile {
     pub metadata: Metadata,
 
     /// A flag variable used to indicate whether
@@ -19,16 +19,16 @@ pub struct DadkManifest {
     pub used_default: bool,
 }
 
-impl DadkManifest {
+impl DadkManifestFile {
     pub fn load(path: &PathBuf) -> Result<Self> {
         // 读取文件内容
         let content = fs::read_to_string(path)?;
-        Self::do_load(&content)
+        Self::load_from_str(&content)
     }
 
-    fn do_load(content: &str) -> Result<Self> {
+    pub fn load_from_str(content: &str) -> Result<Self> {
         // Parse TOML content
-        let mut manifest_toml: DadkManifest = toml::from_str(content)?;
+        let mut manifest_toml: DadkManifestFile = toml::from_str(content)?;
 
         manifest_toml.used_default = check_used_default();
 
@@ -110,7 +110,7 @@ mod tests {
         temp_file.write_all(toml_content.as_bytes())?;
 
         let path = temp_file.path().to_path_buf();
-        let manifest = DadkManifest::load(&path)?;
+        let manifest = DadkManifestFile::load(&path)?;
 
         assert_eq!(manifest.metadata.arch, TargetArch::X86_64);
         assert_eq!(
@@ -134,7 +134,7 @@ mod tests {
     #[test]
     fn test_load_file_not_found() {
         let path = PathBuf::from("non_existent_file.toml");
-        let result = DadkManifest::load(&path);
+        let result = DadkManifestFile::load(&path);
 
         assert!(result.is_err());
     }
@@ -151,7 +151,7 @@ mod tests {
         temp_file.write_all(invalid_toml_content.as_bytes())?;
 
         let path = temp_file.path().to_path_buf();
-        let result = DadkManifest::load(&path);
+        let result = DadkManifestFile::load(&path);
 
         assert!(result.is_err());
 
@@ -171,7 +171,7 @@ mod tests {
         temp_file.write_all(invalid_toml_content.as_bytes())?;
 
         let path = temp_file.path().to_path_buf();
-        let result = DadkManifest::load(&path);
+        let result = DadkManifestFile::load(&path);
 
         assert!(result.is_err());
 
@@ -190,7 +190,7 @@ mod tests {
         temp_file.write_all(toml_content.as_bytes())?;
 
         let path = temp_file.path().to_path_buf();
-        let result = DadkManifest::load(&path);
+        let result = DadkManifestFile::load(&path);
 
         assert!(result.is_err());
 
@@ -208,7 +208,7 @@ mod tests {
         let mut temp_file = NamedTempFile::new()?;
         temp_file.write_all(toml_content.as_bytes())?;
         let path = temp_file.path().to_path_buf();
-        let manifest = DadkManifest::load(&path)?;
+        let manifest = DadkManifestFile::load(&path)?;
         assert_eq!(manifest.used_default, true);
         assert_eq!(
             manifest.metadata.rootfs_config,

+ 52 - 0
dadk-config/src/rootfs/fstype.rs

@@ -0,0 +1,52 @@
+use serde::{Deserialize, Deserializer};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FsType {
+    Fat32,
+}
+
+impl<'de> Deserialize<'de> for FsType {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let mut s = String::deserialize(deserializer)?;
+        s.make_ascii_lowercase();
+        match s.as_str() {
+            "fat32" => Ok(FsType::Fat32),
+            _ => Err(serde::de::Error::custom("invalid fs type")),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use serde_json::{self, Value};
+
+    fn deserialize_fs_type(input: &str) -> Result<FsType, serde_json::Error> {
+        let json = Value::String(input.to_string());
+        serde_json::from_value(json)
+    }
+
+    #[test]
+    fn test_deserialize_fat32_lowercase() {
+        let r = deserialize_fs_type("fat32");
+        assert_eq!(r.is_ok(), true);
+        let fs_type = r.unwrap();
+        assert_eq!(fs_type, FsType::Fat32);
+    }
+
+    #[test]
+    fn test_deserialize_fat32_mixed_case() {
+        let r = deserialize_fs_type("FAT32");
+        assert_eq!(r.is_ok(), true);
+        let fs_type = r.unwrap();
+        assert_eq!(fs_type, FsType::Fat32);
+    }
+
+    #[test]
+    fn testdeserialize_random_string() {
+        assert!(deserialize_fs_type("abc123").is_err());
+    }
+}

+ 151 - 0
dadk-config/src/rootfs/mod.rs

@@ -1 +1,152 @@
+pub mod fstype;
+mod utils;
 
+use std::{fs, path::PathBuf};
+
+use anyhow::Result;
+use fstype::FsType;
+use serde::Deserialize;
+
+/// rootfs配置文件
+#[derive(Debug, Clone, Copy, Deserialize)]
+pub struct RootFSConfigFile {
+    pub metadata: RootFSMeta,
+}
+
+impl RootFSConfigFile {
+    pub fn load(path: &PathBuf) -> Result<Self> {
+        // 读取文件内容
+        let content = fs::read_to_string(path)?;
+        Self::load_from_str(&content)
+    }
+
+    pub fn load_from_str(content: &str) -> Result<Self> {
+        let config: RootFSConfigFile = toml::from_str(content)?;
+
+        Ok(config)
+    }
+}
+
+#[derive(Debug, Clone, Copy, Deserialize)]
+pub struct RootFSMeta {
+    /// rootfs文件系统类型
+    pub fs_type: FsType,
+    /// rootfs磁盘大小(至少要大于这个值)
+    #[serde(deserialize_with = "utils::size::deserialize_size")]
+    pub size: usize,
+}
+
+#[cfg(test)]
+mod tests {
+    use std::io::Write;
+
+    use super::*;
+    use tempfile::NamedTempFile;
+
+    #[test]
+    fn test_load_from_valid_file() {
+        let config_content = r#"
+            [metadata]
+            fs_type = "fat32"
+            size = "1024M"
+        "#;
+
+        let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
+        temp_file
+            .write_all(config_content.as_bytes())
+            .expect("Failed to write to temp file");
+
+        let config_path = PathBuf::from(temp_file.path());
+        let config = RootFSConfigFile::load(&config_path).expect("Failed to load config");
+
+        assert_eq!(config.metadata.fs_type, FsType::Fat32);
+        assert_eq!(config.metadata.size, 1024 * 1024 * 1024); // Assuming `deserialize_size` converts MB to Bytes
+    }
+
+    #[test]
+    fn test_load_from_valid_str() {
+        let config_content = r#"
+            [metadata]
+            fs_type = "fat32"
+            size = "512M"
+        "#;
+
+        let config = RootFSConfigFile::load_from_str(config_content)
+            .expect("Failed to load config from str");
+
+        assert_eq!(config.metadata.fs_type, FsType::Fat32);
+        assert_eq!(config.metadata.size, 512 * 1024 * 1024); // Assuming `deserialize_size` converts MB to Bytes
+    }
+    #[test]
+    fn test_load_from_invalid_fs_type() {
+        let config_content = r#"
+            [metadata]
+            fs_type = "ABCDE"
+            size = "512M"
+        "#;
+        assert!(RootFSConfigFile::load_from_str(config_content).is_err());
+    }
+
+    /// 测试size为int类型的字节大小
+    #[test]
+    fn test_load_from_valid_str_size_integer() {
+        let config_content = r#"
+            [metadata]
+            fs_type = "fat32"
+            size = 1048576
+        "#;
+
+        let config = RootFSConfigFile::load_from_str(config_content)
+            .expect("Failed to load config from str");
+
+        assert_eq!(config.metadata.fs_type, FsType::Fat32);
+        assert_eq!(config.metadata.size, 1048576); // Assuming `deserialize_size` converts MB to Bytes
+    }
+    #[test]
+    fn test_load_from_valid_str_size_bytes_str() {
+        let config_content = r#"
+            [metadata]
+            fs_type = "fat32"
+            size = "1048576"
+        "#;
+
+        let config = RootFSConfigFile::load_from_str(config_content)
+            .expect("Failed to load config from str");
+
+        assert_eq!(config.metadata.fs_type, FsType::Fat32);
+        assert_eq!(config.metadata.size, 1048576); // Assuming `deserialize_size` converts MB to Bytes
+    }
+
+    #[test]
+    fn test_load_from_invalid_file() {
+        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
+        let config_path = PathBuf::from(temp_file.path());
+
+        assert!(RootFSConfigFile::load(&config_path).is_err());
+    }
+
+    /// Parse from an incorrect size field (string)
+    #[test]
+    fn test_load_from_invalid_size_str() {
+        let invalid_config_content = r#"
+            [metadata]
+            fs_type = "fat32"
+            size = "not_a_size"
+        "#;
+
+        assert!(RootFSConfigFile::load_from_str(invalid_config_content).is_err());
+    }
+
+    /// Parse from an incorrect size field (array)
+    #[test]
+    fn test_load_from_invalid_size_array() {
+        // The 'size' field should not be an array
+        let invalid_config_content = r#"
+            [metadata]
+            fs_type = "fat32"
+            size = ["not_a_size"]
+        "#;
+
+        assert!(RootFSConfigFile::load_from_str(invalid_config_content).is_err());
+    }
+}

+ 1 - 0
dadk-config/src/rootfs/utils/mod.rs

@@ -0,0 +1 @@
+pub mod size;

+ 118 - 0
dadk-config/src/rootfs/utils/size.rs

@@ -0,0 +1,118 @@
+use serde::Deserializer;
+
+/// 自定义反序列化函数,用于解析表示磁盘镜像大小的值。
+///
+/// 此函数支持两种输入格式:
+/// 1. 纯数字:直接将其视为字节数。
+/// 2. 带单位的字符串:如"1M"、"1G",其中单位支持K(千字节)、M(兆字节)、G(千兆字节)。
+///
+/// 函数将输入值解析为`usize`类型,表示字节数。
+///
+/// # 参数
+/// - `deserializer`: 一个实现了`Deserializer` trait的对象,用于读取和解析输入数据。
+///
+/// # 返回值
+/// 返回一个`Result<usize, D::Error>`,其中:
+/// - `Ok(usize)`表示解析成功,返回对应的字节数。
+/// - `Err(D::Error)`表示解析失败,返回错误信息。
+///
+/// # 错误处理
+/// - 如果输入是非法的字符串(无法解析或单位不合法),将返回自定义错误。
+/// - 如果输入类型既不是整数也不是字符串,将返回类型错误。
+pub fn deserialize_size<'de, D>(deserializer: D) -> Result<usize, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    // 使用serde的deserialize_any方法来处理不同类型的输入
+    let value = serde::de::Deserialize::deserialize(deserializer)?;
+
+    // 匹配输入值的类型,进行相应的转换
+    match value {
+        toml::Value::Integer(num) => {
+            // 如果是整数类型,直接转换成usize
+            Ok(num as usize)
+        }
+        toml::Value::String(s) => {
+            // 如果是字符串类型,解析如"1M"这样的表示
+            parse_size_from_string(&s)
+                .ok_or_else(|| serde::de::Error::custom("Invalid string for size"))
+        }
+        _ => Err(serde::de::Error::custom("Invalid type for size")),
+    }
+}
+
+/// Parses a size string with optional unit suffix (K, M, G) into a usize value.
+///
+/// This function takes a string that represents a size, which can be a plain
+/// number or a number followed by a unit suffix (K for kilobytes, M for megabytes,
+/// G for gigabytes). It converts this string into an equivalent usize value in bytes.
+///
+/// # Parameters
+/// - `size_str`: A string slice that contains the size to parse. This can be a simple
+///   numeric string or a numeric string followed by a unit ('K', 'M', 'G').
+///
+/// # Returns
+/// An `Option<usize>` where:
+/// - `Some(usize)` contains the parsed size in bytes if the input string is valid.
+/// - `None` if the input string is invalid or contains an unsupported unit.
+fn parse_size_from_string(size_str: &str) -> Option<usize> {
+    if size_str.chars().all(|c| c.is_ascii_digit()) {
+        // 如果整个字符串都是数字,直接解析返回
+        return size_str.parse::<usize>().ok();
+    }
+
+    let mut chars = size_str.chars().rev();
+    let unit = chars.next()?;
+    let number_str: String = chars.rev().collect();
+    let number = number_str.parse::<usize>().ok()?;
+
+    match unit.to_ascii_uppercase() {
+        'K' => Some(number * 1024),
+        'M' => Some(number * 1024 * 1024),
+        'G' => Some(number * 1024 * 1024 * 1024),
+        _ => None,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_parse_size_from_string() {
+        // 正常情况,不带单位
+        assert_eq!(parse_size_from_string("1024"), Some(1024));
+
+        // 正常情况,带有单位
+        assert_eq!(parse_size_from_string("1K"), Some(1024));
+        assert_eq!(parse_size_from_string("2M"), Some(2 * 1024 * 1024));
+        assert_eq!(parse_size_from_string("3G"), Some(3 * 1024 * 1024 * 1024));
+
+        // 边界情况
+        assert_eq!(parse_size_from_string("0K"), Some(0));
+        assert_eq!(parse_size_from_string("0M"), Some(0));
+        assert_eq!(parse_size_from_string("0G"), Some(0));
+
+        // 小写情况
+        assert_eq!(parse_size_from_string("1k"), Some(1024));
+        assert_eq!(parse_size_from_string("2m"), Some(2 * 1024 * 1024));
+        assert_eq!(parse_size_from_string("3g"), Some(3 * 1024 * 1024 * 1024));
+
+        // 错误的单位
+        assert_eq!(parse_size_from_string("1T"), None);
+        assert_eq!(parse_size_from_string("2X"), None);
+
+        // 错误的数字格式
+        assert_eq!(parse_size_from_string("aK"), None);
+        assert_eq!(parse_size_from_string("1.5M"), None);
+
+        // 空字符串
+        assert_eq!(parse_size_from_string(""), None);
+
+        // 只单位没有数字
+        assert_eq!(parse_size_from_string("K"), None);
+
+        // 数字后有多余字符
+        assert_eq!(parse_size_from_string("1KextrK"), None);
+    }
+}

+ 5 - 0
dadk-config/templates/config/rootfs.toml

@@ -0,0 +1,5 @@
+[metadata]
+# Filesystem type (options: `fat32`)
+fs_type = "fat32"
+# Size of the rootfs disk image (eg, `1G`, `1024M`)
+size = "1G"

+ 3 - 4
dadk-config/tests/test_dadk_manifest.rs

@@ -1,19 +1,18 @@
-use dadk_config::{self, manifest::DadkManifest};
+use dadk_config::{self, manifest::DadkManifestFile};
 use test_base::{
     dadk_config::DadkConfigTestContext,
     test_context::{self as test_context, test_context},
 };
 
-const TEMPLATES_DIR: &str = "templates";
 const DADK_MANIFEST_FILE_NAME: &str = "dadk-manifest.toml";
 
 /// 测试加载模板目录中的 dadk-manifest.toml 文件,验证它能被加载成功,并且已经包含了所有字段
 #[test_context(DadkConfigTestContext)]
 #[test]
 fn test_load_dadk_manifest_template(ctx: &DadkConfigTestContext) {
-    let manifest_path = ctx.abs_path(&format!("{TEMPLATES_DIR}/{DADK_MANIFEST_FILE_NAME}"));
+    let manifest_path = ctx.templates_dir().join(DADK_MANIFEST_FILE_NAME);
     assert_eq!(manifest_path.exists(), true);
     assert_eq!(manifest_path.is_file(), true);
-    let manifest = DadkManifest::load(&manifest_path).expect("Failed to load manifest");
+    let manifest = DadkManifestFile::load(&manifest_path).expect("Failed to load manifest");
     assert_eq!(manifest.used_default, false);
 }

+ 19 - 0
dadk-config/tests/test_rootfs_config.rs

@@ -0,0 +1,19 @@
+use dadk_config::{self, rootfs::RootFSConfigFile};
+use test_base::{
+    dadk_config::DadkConfigTestContext,
+    test_context::{self as test_context, test_context},
+};
+
+const ROOTFS_MANIFEST_FILE_NAME: &str = "config/rootfs.toml";
+
+/// 测试加载模板目录中的 rootfs.toml 文件,验证它能被加载成功,并且已经包含了所有字段
+#[test_context(DadkConfigTestContext)]
+#[test]
+fn test_load_rootfs_manifest_template(ctx: &DadkConfigTestContext) {
+    let rootfs_manifest_path = ctx.templates_dir().join(ROOTFS_MANIFEST_FILE_NAME);
+    assert_eq!(rootfs_manifest_path.exists(), true);
+    assert_eq!(rootfs_manifest_path.is_file(), true);
+    let _manifest =
+        RootFSConfigFile::load(&rootfs_manifest_path).expect("Failed to load rootfs manifest");
+    // TODO 校验 manifest 中的字段是否齐全
+}