multiboot2/
module.rs

1//! Module for [`ModuleTag`].
2
3use crate::tag::TagHeader;
4use crate::{StringError, TagIter, TagType, parse_slice_as_string};
5use core::fmt::{Debug, Formatter};
6use core::mem;
7use multiboot2_common::{MaybeDynSized, Tag};
8#[cfg(feature = "builder")]
9use {alloc::boxed::Box, multiboot2_common::new_boxed};
10
11/// The module tag can occur multiple times and specifies passed boot modules
12/// (blobs in memory). The tag itself doesn't include the blog, but references
13/// its location.
14#[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[repr(C, align(8))]
16pub struct ModuleTag {
17    header: TagHeader,
18    mod_start: u32,
19    mod_end: u32,
20    /// Null-terminated UTF-8 string
21    cmdline: [u8],
22}
23
24impl ModuleTag {
25    /// Constructs a new tag.
26    #[cfg(feature = "builder")]
27    #[must_use]
28    pub fn new(start: u32, end: u32, cmdline: &str) -> Box<Self> {
29        let header = TagHeader::new(Self::ID, 0);
30        assert!(end > start, "must have a size");
31
32        let start = start.to_ne_bytes();
33        let end = end.to_ne_bytes();
34        let cmdline = cmdline.as_bytes();
35
36        if cmdline.ends_with(&[0]) {
37            new_boxed(header, &[&start, &end, cmdline])
38        } else {
39            new_boxed(header, &[&start, &end, cmdline, &[0]])
40        }
41    }
42
43    /// Reads the command line of the boot module as Rust string slice without
44    /// the null-byte.
45    /// This is an null-terminated UTF-8 string. If this returns `Err` then perhaps the memory
46    /// is invalid or the bootloader doesn't follow the spec.
47    ///
48    /// For example, this returns `"--test cmdline-option"`.if the GRUB config
49    /// contains  `"module2 /some_boot_module --test cmdline-option"`.
50    ///
51    /// If the function returns `Err` then perhaps the memory is invalid.
52    pub fn cmdline(&self) -> Result<&str, StringError> {
53        parse_slice_as_string(&self.cmdline)
54    }
55
56    /// Start address of the module.
57    #[must_use]
58    pub const fn start_address(&self) -> u32 {
59        self.mod_start
60    }
61
62    /// End address of the module
63    #[must_use]
64    pub const fn end_address(&self) -> u32 {
65        self.mod_end
66    }
67
68    /// The size of the module/the BLOB in memory.
69    #[must_use]
70    pub const fn module_size(&self) -> u32 {
71        self.mod_end - self.mod_start
72    }
73}
74
75impl MaybeDynSized for ModuleTag {
76    type Header = TagHeader;
77
78    const BASE_SIZE: usize = mem::size_of::<TagHeader>() + 2 * mem::size_of::<u32>();
79
80    fn dst_len(header: &TagHeader) -> usize {
81        assert!(header.size as usize >= Self::BASE_SIZE);
82        header.size as usize - Self::BASE_SIZE
83    }
84}
85
86impl Tag for ModuleTag {
87    type IDType = TagType;
88
89    const ID: TagType = TagType::Module;
90}
91
92impl Debug for ModuleTag {
93    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
94        f.debug_struct("ModuleTag")
95            .field("type", &self.header.typ)
96            .field("size", &self.header.size)
97            // Trick to print as hex.
98            .field("mod_start", &self.mod_start)
99            .field("mod_end", &self.mod_end)
100            .field("mod_size", &self.module_size())
101            .field("cmdline", &self.cmdline())
102            .finish()
103    }
104}
105
106pub const fn module_iter(iter: TagIter) -> ModuleIter {
107    ModuleIter { iter }
108}
109
110/// An iterator over all module tags.
111#[derive(Clone)]
112pub struct ModuleIter<'a> {
113    iter: TagIter<'a>,
114}
115
116impl<'a> Iterator for ModuleIter<'a> {
117    type Item = &'a ModuleTag;
118
119    fn next(&mut self) -> Option<&'a ModuleTag> {
120        self.iter
121            .find(|tag| tag.header().typ == TagType::Module)
122            .map(|tag| tag.cast())
123    }
124}
125
126impl Debug for ModuleIter<'_> {
127    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
128        let mut list = f.debug_list();
129        self.clone().for_each(|tag| {
130            list.entry(&tag);
131        });
132        list.finish()
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::GenericInfoTag;
140    use core::borrow::Borrow;
141    use multiboot2_common::test_utils::AlignedBytes;
142
143    #[rustfmt::skip]
144    fn get_bytes() -> AlignedBytes<24> {
145        AlignedBytes::new([
146            TagType::Module.val() as u8, 0, 0, 0,
147            22, 0, 0, 0,
148            /* mod start */
149            0x00, 0xff, 0, 0,
150            /* mod end */
151            0xff, 0xff, 0, 0,
152            b'h', b'e', b'l', b'l', b'o', b'\0',
153            /* padding */
154            0, 0,
155        ])
156    }
157
158    /// Tests to parse a string with a terminating null byte from the tag (as the spec defines).
159    #[test]
160    fn test_parse_str() {
161        let bytes = get_bytes();
162        let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap();
163        let tag = tag.cast::<ModuleTag>();
164        assert_eq!(tag.header.typ, TagType::Module);
165        assert_eq!(tag.cmdline(), Ok("hello"));
166    }
167
168    /// Test to generate a tag from a given string.
169    #[test]
170    #[cfg(feature = "builder")]
171    fn test_build_str() {
172        let tag = ModuleTag::new(0xff00, 0xffff, "hello");
173        let bytes = tag.as_bytes().as_ref();
174        let bytes = &bytes[..tag.header.size as usize];
175        assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]);
176        assert_eq!(tag.cmdline(), Ok("hello"));
177
178        // With terminating null.
179        let tag = ModuleTag::new(0xff00, 0xffff, "hello\0");
180        let bytes = tag.as_bytes().as_ref();
181        let bytes = &bytes[..tag.header.size as usize];
182        assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]);
183        assert_eq!(tag.cmdline(), Ok("hello"));
184
185        // test also some bigger message
186        let tag = ModuleTag::new(0, 1, "AbCdEfGhUjK YEAH");
187        assert_eq!(tag.cmdline(), Ok("AbCdEfGhUjK YEAH"));
188        let tag = ModuleTag::new(0, 1, "AbCdEfGhUjK YEAH".repeat(42).as_str());
189        assert_eq!(tag.cmdline(), Ok("AbCdEfGhUjK YEAH".repeat(42).as_str()));
190    }
191}