Skip to content

Commit dedb4f6

Browse files
committed
multiboot2: use dst for tags where applicable
This commit transforms a few tags where applicable to DSTs. This better fits into the Rust type system and makes a few things easier, such as parsing the cmdline string from the command line tag. Depending on how users used the tags, this change is not even breaking. Additionally, there is now a public trait which allows custom tag users to also benefit from DSTs.
1 parent 3b9c72f commit dedb4f6

File tree

8 files changed

+352
-106
lines changed

8 files changed

+352
-106
lines changed

Cargo.lock

+21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

multiboot2/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ unstable = []
4040
bitflags = "1"
4141
derive_more = { version = "0.99", default-features = false, features = ["display"] }
4242
log = { version = "0.4", default-features = false }
43+
ptr_meta = { version = "0.2.0", default-features = false }

multiboot2/Changelog.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# CHANGELOG for crate `multiboot2`
22

3+
## unreleased
4+
- Add `TagTrait` trait which enables to use DSTs as multiboot2 tags. This is
5+
mostly relevant for the command line tag, the modules tag, and the bootloader
6+
name tag. However, this might also be relevant for users of custom multiboot2
7+
tags that use DSTs as types. See the example provided in the doc of the
8+
`get_tag` method.
9+
310
## 0.15.1 (2023-03-18)
411
- **BREAKING** `MemoryMapTag::all_memory_areas()` was renamed to `memory_areas`
512
and now returns `MemoryAreaIter` instead of

multiboot2/src/boot_loader_name.rs

+35-22
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1-
use crate::TagTypeId;
1+
use crate::TagTrait;
2+
use crate::{Tag, TagTypeId};
3+
use core::fmt::{Debug, Formatter};
24
use core::str::Utf8Error;
35

4-
/// This tag contains the name of the bootloader that is booting the kernel.
5-
///
6-
/// The name is a normal C-style UTF-8 zero-terminated string that can be
7-
/// obtained via the `name` method.
8-
#[derive(Clone, Copy, Debug)]
6+
/// The bootloader name tag.
7+
#[derive(ptr_meta::Pointee)]
98
#[repr(C, packed)] // only repr(C) would add unwanted padding before first_section
109
pub struct BootLoaderNameTag {
1110
typ: TagTypeId,
1211
size: u32,
1312
/// Null-terminated UTF-8 string
14-
string: u8,
13+
name: [u8],
1514
}
1615

1716
impl BootLoaderNameTag {
18-
/// Read the name of the bootloader that is booting the kernel.
19-
/// This is an null-terminated UTF-8 string. If this returns `Err` then perhaps the memory
20-
/// is invalid or the bootloader doesn't follow the spec.
17+
/// Reads the name of the bootloader that is booting the kernel as Rust
18+
/// string slice without the null-byte.
19+
///
20+
/// For example, this returns `"GRUB 2.02~beta3-5"`.
21+
///
22+
/// If the function returns `Err` then perhaps the memory is invalid.
2123
///
2224
/// # Examples
2325
///
@@ -28,17 +30,32 @@ impl BootLoaderNameTag {
2830
/// }
2931
/// ```
3032
pub fn name(&self) -> Result<&str, Utf8Error> {
31-
use core::{mem, slice, str};
32-
// strlen without null byte
33-
let strlen = self.size as usize - mem::size_of::<BootLoaderNameTag>();
34-
let bytes = unsafe { slice::from_raw_parts((&self.string) as *const u8, strlen) };
35-
str::from_utf8(bytes)
33+
Tag::get_dst_str_slice(&self.name)
34+
}
35+
}
36+
37+
impl Debug for BootLoaderNameTag {
38+
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
39+
f.debug_struct("BootLoaderNameTag")
40+
.field("typ", &{ self.typ })
41+
.field("size", &{ self.size })
42+
.field("name", &self.name())
43+
.finish()
44+
}
45+
}
46+
47+
impl TagTrait for BootLoaderNameTag {
48+
fn dst_size(base_tag: &Tag) -> usize {
49+
// The size of the sized portion of the bootloader name tag.
50+
let tag_base_size = 8;
51+
assert!(base_tag.size >= 8);
52+
base_tag.size as usize - tag_base_size
3653
}
3754
}
3855

3956
#[cfg(test)]
4057
mod tests {
41-
use crate::TagType;
58+
use crate::{BootLoaderNameTag, Tag, TagType};
4259

4360
const MSG: &str = "hello";
4461

@@ -63,12 +80,8 @@ mod tests {
6380
#[test]
6481
fn test_parse_str() {
6582
let tag = get_bytes();
66-
let tag = unsafe {
67-
tag.as_ptr()
68-
.cast::<super::BootLoaderNameTag>()
69-
.as_ref()
70-
.unwrap()
71-
};
83+
let tag = unsafe { &*tag.as_ptr().cast::<Tag>() };
84+
let tag = tag.cast_tag::<BootLoaderNameTag>();
7285
assert_eq!({ tag.typ }, TagType::BootLoaderName);
7386
assert_eq!(tag.name().expect("must be valid UTF-8"), MSG);
7487
}

multiboot2/src/command_line.rs

+34-19
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
//! Module for [CommandLineTag].
22
3-
use crate::TagTypeId;
4-
use core::mem;
5-
use core::slice;
3+
use crate::{Tag, TagTrait, TagTypeId};
4+
use core::fmt::{Debug, Formatter};
65
use core::str;
76

87
/// This tag contains the command line string.
98
///
109
/// The string is a normal C-style UTF-8 zero-terminated string that can be
1110
/// obtained via the `command_line` method.
12-
#[derive(Clone, Copy, Debug)]
1311
#[repr(C, packed)] // only repr(C) would add unwanted padding before first_section
12+
#[derive(ptr_meta::Pointee)]
1413
pub struct CommandLineTag {
1514
typ: TagTypeId,
1615
size: u32,
1716
/// Null-terminated UTF-8 string
18-
string: u8,
17+
cmdline: [u8],
1918
}
2019

2120
impl CommandLineTag {
22-
/// Read the command line string that is being passed to the booting kernel.
23-
/// This is an null-terminated UTF-8 string. If this returns `Err` then perhaps the memory
24-
/// is invalid or the bootloader doesn't follow the spec.
21+
/// Reads the command line of the kernel as Rust string slice without
22+
/// the null-byte.
23+
///
24+
/// For example, this returns `"console=ttyS0"`.if the GRUB config
25+
/// contains `"multiboot2 /mykernel console=ttyS0"`.
26+
///
27+
/// If the function returns `Err` then perhaps the memory is invalid.
2528
///
2629
/// # Examples
2730
///
@@ -33,16 +36,32 @@ impl CommandLineTag {
3336
/// }
3437
/// ```
3538
pub fn command_line(&self) -> Result<&str, str::Utf8Error> {
36-
// strlen without null byte
37-
let strlen = self.size as usize - mem::size_of::<CommandLineTag>();
38-
let bytes = unsafe { slice::from_raw_parts((&self.string) as *const u8, strlen) };
39-
str::from_utf8(bytes)
39+
Tag::get_dst_str_slice(&self.cmdline)
40+
}
41+
}
42+
43+
impl Debug for CommandLineTag {
44+
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
45+
f.debug_struct("CommandLineTag")
46+
.field("typ", &{ self.typ })
47+
.field("size", &{ self.size })
48+
.field("cmdline", &self.command_line())
49+
.finish()
50+
}
51+
}
52+
53+
impl TagTrait for CommandLineTag {
54+
fn dst_size(base_tag: &Tag) -> usize {
55+
// The size of the sized portion of the command line tag.
56+
let tag_base_size = 8;
57+
assert!(base_tag.size >= 8);
58+
base_tag.size as usize - tag_base_size
4059
}
4160
}
4261

4362
#[cfg(test)]
4463
mod tests {
45-
use crate::TagType;
64+
use crate::{CommandLineTag, Tag, TagType};
4665

4766
const MSG: &str = "hello";
4867

@@ -67,12 +86,8 @@ mod tests {
6786
#[test]
6887
fn test_parse_str() {
6988
let tag = get_bytes();
70-
let tag = unsafe {
71-
tag.as_ptr()
72-
.cast::<super::CommandLineTag>()
73-
.as_ref()
74-
.unwrap()
75-
};
89+
let tag = unsafe { &*tag.as_ptr().cast::<Tag>() };
90+
let tag = tag.cast_tag::<CommandLineTag>();
7691
assert_eq!({ tag.typ }, TagType::Cmdline);
7792
assert_eq!(tag.command_line().expect("must be valid UTF-8"), MSG);
7893
}

0 commit comments

Comments
 (0)