multiboot2/
command_line.rs

1//! Module for [`CommandLineTag`].
2
3use crate::tag::TagHeader;
4use crate::{StringError, TagType, parse_slice_as_string};
5use core::fmt::{Debug, Formatter};
6use core::mem;
7use core::str;
8use multiboot2_common::{MaybeDynSized, Tag};
9#[cfg(feature = "builder")]
10use {alloc::boxed::Box, multiboot2_common::new_boxed};
11
12/// This tag contains the command line string.
13///
14/// The string is a normal C-style UTF-8 zero-terminated string that can be
15/// obtained via the `command_line` method.
16#[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[repr(C, align(8))]
18pub struct CommandLineTag {
19    header: TagHeader,
20    /// Null-terminated UTF-8 string
21    cmdline: [u8],
22}
23
24impl CommandLineTag {
25    /// Create a new command line tag from the given string.
26    #[cfg(feature = "builder")]
27    #[must_use]
28    pub fn new(command_line: &str) -> Box<Self> {
29        let header = TagHeader::new(Self::ID, 0);
30        let bytes = command_line.as_bytes();
31        if bytes.ends_with(&[0]) {
32            new_boxed(header, &[bytes])
33        } else {
34            new_boxed(header, &[bytes, &[0]])
35        }
36    }
37
38    /// Reads the command line of the kernel as Rust string slice without
39    /// the null-byte.
40    ///
41    /// For example, this returns `"console=ttyS0"`.if the GRUB config
42    /// contains  `"multiboot2 /mykernel console=ttyS0"`.
43    ///
44    /// If the function returns `Err` then perhaps the memory is invalid.
45    ///
46    /// # Examples
47    ///
48    /// ```rust,no_run
49    /// # use multiboot2::{BootInformation, BootInformationHeader};
50    /// # let ptr = 0xdeadbeef as *const BootInformationHeader;
51    /// # let boot_info = unsafe { BootInformation::load(ptr).unwrap() };
52    /// if let Some(tag) = boot_info.command_line_tag() {
53    ///     let command_line = tag.cmdline();
54    ///     assert_eq!(Ok("/bootarg"), command_line);
55    /// }
56    /// ```
57    pub fn cmdline(&self) -> Result<&str, StringError> {
58        parse_slice_as_string(&self.cmdline)
59    }
60}
61
62impl Debug for CommandLineTag {
63    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
64        f.debug_struct("CommandLineTag")
65            .field("typ", &self.header.typ)
66            .field("size", &self.header.size)
67            .field("cmdline", &self.cmdline())
68            .finish()
69    }
70}
71
72impl MaybeDynSized for CommandLineTag {
73    type Header = TagHeader;
74
75    const BASE_SIZE: usize = mem::size_of::<TagHeader>();
76
77    fn dst_len(header: &TagHeader) -> usize {
78        assert!(header.size as usize >= Self::BASE_SIZE);
79        header.size as usize - Self::BASE_SIZE
80    }
81}
82
83impl Tag for CommandLineTag {
84    type IDType = TagType;
85
86    const ID: TagType = TagType::Cmdline;
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::GenericInfoTag;
93    use core::borrow::Borrow;
94    use multiboot2_common::test_utils::AlignedBytes;
95
96    #[rustfmt::skip]
97    fn get_bytes() -> AlignedBytes<16> {
98        AlignedBytes::new([
99            TagType::Cmdline.val() as u8, 0, 0, 0,
100            14, 0, 0, 0,
101            b'h', b'e', b'l', b'l', b'o',  b'\0',
102            /* padding */
103            0, 0
104        ])
105    }
106
107    /// Tests to parse a string with a terminating null byte from the tag (as the spec defines).
108    #[test]
109    fn test_parse_str() {
110        let bytes = get_bytes();
111        let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap();
112        let tag = tag.cast::<CommandLineTag>();
113        assert_eq!(tag.header.typ, TagType::Cmdline);
114        assert_eq!(tag.cmdline(), Ok("hello"));
115    }
116
117    /// Test to generate a tag from a given string.
118    #[test]
119    #[cfg(feature = "builder")]
120    fn test_build_str() {
121        let tag = CommandLineTag::new("hello");
122        let bytes = tag.as_bytes().as_ref();
123        let bytes = &bytes[..tag.header.size as usize];
124        assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]);
125        assert_eq!(tag.cmdline(), Ok("hello"));
126
127        // With terminating null.
128        let tag = CommandLineTag::new("hello\0");
129        let bytes = tag.as_bytes().as_ref();
130        let bytes = &bytes[..tag.header.size as usize];
131        assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]);
132        assert_eq!(tag.cmdline(), Ok("hello"));
133
134        // test also some bigger message
135        let tag = CommandLineTag::new("AbCdEfGhUjK YEAH");
136        assert_eq!(tag.cmdline(), Ok("AbCdEfGhUjK YEAH"));
137        let tag = CommandLineTag::new("AbCdEfGhUjK YEAH".repeat(42).as_str());
138        assert_eq!(tag.cmdline(), Ok("AbCdEfGhUjK YEAH".repeat(42).as_str()));
139    }
140}