Skip to main content

squib_api/schemas/
boot_source.rs

1//! `/boot-source` PUT body.
2//!
3//! Per [21-api-compat-matrix.md
4//! `/boot-source`](../../../specs/21-api-compat-matrix.md#boot-source):
5//!
6//! - `kernel_image_path` — required; validated as `SafePath` (1024-byte cap, no NUL).
7//! - `initrd_path` — optional; same validation.
8//! - `boot_args` — optional; capped at 4096 bytes (Linux `COMMAND_LINE_SIZE` is typically 256–4096
9//!   depending on architecture; we use 4096 as the practical upper bound). The FDT builder applies
10//!   the D23 composition rule downstream — this layer passes the user value through verbatim.
11
12use serde::{Deserialize, Serialize};
13
14use super::common::SafePath;
15
16/// Maximum length of the `boot_args` cmdline string, in bytes.
17pub const BOOT_ARGS_MAX: usize = 4096;
18
19/// Raw `/boot-source` PUT body, exactly off the wire.
20#[derive(Debug, Clone, Deserialize)]
21#[serde(deny_unknown_fields)]
22pub struct RawBootSourceConfig {
23    /// Host-filesystem path to the kernel image.
24    pub kernel_image_path: String,
25    /// Host-filesystem path to the initrd, if any.
26    #[serde(default)]
27    pub initrd_path: Option<String>,
28    /// Kernel command line. The FDT builder applies the D23 append rules.
29    #[serde(default)]
30    pub boot_args: Option<String>,
31}
32
33/// Validated `/boot-source` PUT body.
34#[derive(Debug, Clone, Serialize)]
35#[non_exhaustive]
36pub struct BootSourceConfig {
37    /// Validated kernel-image path.
38    pub kernel_image_path: SafePath,
39    /// Optional validated initrd path.
40    pub initrd_path: Option<SafePath>,
41    /// Validated kernel command line (`boot_args`).
42    pub boot_args: Option<String>,
43}
44
45impl TryFrom<RawBootSourceConfig> for BootSourceConfig {
46    type Error = String;
47
48    fn try_from(raw: RawBootSourceConfig) -> Result<Self, Self::Error> {
49        let kernel_image_path = SafePath::new(raw.kernel_image_path)
50            .map_err(|e| format!("Invalid kernel_image_path: {e}"))?;
51        let initrd_path = match raw.initrd_path {
52            Some(p) => Some(SafePath::new(p).map_err(|e| format!("Invalid initrd_path: {e}"))?),
53            None => None,
54        };
55        if let Some(args) = raw.boot_args.as_deref() {
56            if args.len() > BOOT_ARGS_MAX {
57                return Err(format!(
58                    "Invalid boot_args: exceeds {BOOT_ARGS_MAX} bytes (got {} bytes)",
59                    args.len()
60                ));
61            }
62            if args.contains('\0') {
63                return Err("Invalid boot_args: must not contain NUL bytes".into());
64            }
65        }
66        Ok(Self {
67            kernel_image_path,
68            initrd_path,
69            boot_args: raw.boot_args,
70        })
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_should_accept_minimal_boot_source() {
80        let raw = RawBootSourceConfig {
81            kernel_image_path: "/tmp/vmlinux.bin".into(),
82            initrd_path: None,
83            boot_args: None,
84        };
85        let cfg = BootSourceConfig::try_from(raw).unwrap();
86        assert_eq!(
87            cfg.kernel_image_path.as_path().as_os_str(),
88            "/tmp/vmlinux.bin"
89        );
90        assert!(cfg.initrd_path.is_none());
91    }
92
93    #[test]
94    fn test_should_reject_empty_kernel_path() {
95        let raw = RawBootSourceConfig {
96            kernel_image_path: String::new(),
97            initrd_path: None,
98            boot_args: None,
99        };
100        assert!(BootSourceConfig::try_from(raw).is_err());
101    }
102
103    #[test]
104    fn test_should_reject_oversize_boot_args() {
105        let raw = RawBootSourceConfig {
106            kernel_image_path: "/tmp/vmlinux.bin".into(),
107            initrd_path: None,
108            boot_args: Some("a".repeat(BOOT_ARGS_MAX + 1)),
109        };
110        let err = BootSourceConfig::try_from(raw).unwrap_err();
111        assert!(err.contains("boot_args"));
112    }
113
114    #[test]
115    fn test_should_reject_nul_in_boot_args() {
116        let raw = RawBootSourceConfig {
117            kernel_image_path: "/tmp/vmlinux.bin".into(),
118            initrd_path: None,
119            boot_args: Some("rw\0bad".into()),
120        };
121        assert!(BootSourceConfig::try_from(raw).is_err());
122    }
123
124    #[test]
125    fn test_should_reject_unknown_fields() {
126        let json = r#"{"kernel_image_path":"/k","unknown":1}"#;
127        assert!(serde_json::from_str::<RawBootSourceConfig>(json).is_err());
128    }
129
130    #[test]
131    fn test_should_round_trip_through_serde() {
132        let json = r#"{"kernel_image_path":"/tmp/k","initrd_path":"/tmp/i","boot_args":"console=ttyAMA0"}"#;
133        let raw: RawBootSourceConfig = serde_json::from_str(json).unwrap();
134        let cfg = BootSourceConfig::try_from(raw).unwrap();
135        assert_eq!(cfg.boot_args.as_deref(), Some("console=ttyAMA0"));
136    }
137}