Skip to content

Commit

Permalink
multiboot2: use dst for tags where applicable
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
phip1611 committed May 15, 2023
1 parent e89b493 commit 64904c1
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 106 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions multiboot2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ unstable = []
bitflags = "1"
derive_more = { version = "0.99", default-features = false, features = ["display"] }
log = { version = "0.4", default-features = false }
ptr_meta = { version = "0.2.0", default-features = false }
7 changes: 7 additions & 0 deletions multiboot2/Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG for crate `multiboot2`

## unreleased
- Add `TagTrait` trait which enables to use DSTs as multiboot2 tags. This is
mostly relevant for the command line tag, the modules tag, and the bootloader
name tag. However, this might also be relevant for users of custom multiboot2
tags that use DSTs as types. See the example provided in the doc of the
`get_tag` method.

## 0.15.1 (2023-03-18)
- **BREAKING** `MemoryMapTag::all_memory_areas()` was renamed to `memory_areas`
and now returns `MemoryAreaIter` instead of
Expand Down
56 changes: 34 additions & 22 deletions multiboot2/src/boot_loader_name.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
use crate::TagTypeId;
use crate::{Tag, TagTypeId};
use core::fmt::{Debug, Formatter};
use core::str::Utf8Error;

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

impl BootLoaderNameTag {
/// Read the name of the bootloader that is booting the kernel.
/// This is an null-terminated UTF-8 string. If this returns `Err` then perhaps the memory
/// is invalid or the bootloader doesn't follow the spec.
/// Reads the name of the bootloader that is booting the kernel as Rust
/// string slice without the null-byte.
///
/// For example, this returns `"GRUB 2.02~beta3-5"`.
///
/// If the function returns `Err` then perhaps the memory is invalid.
///
/// # Examples
///
Expand All @@ -28,17 +29,32 @@ impl BootLoaderNameTag {
/// }
/// ```
pub fn name(&self) -> Result<&str, Utf8Error> {
use core::{mem, slice, str};
// strlen without null byte
let strlen = self.size as usize - mem::size_of::<BootLoaderNameTag>();
let bytes = unsafe { slice::from_raw_parts((&self.string) as *const u8, strlen) };
str::from_utf8(bytes)
Tag::get_dst_str_slice(&self.name)
}
}

impl Debug for BootLoaderNameTag {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("BootLoaderNameTag")
.field("typ", &{ self.typ })
.field("size", &{ self.size })
.field("name", &self.name())
.finish()
}
}

impl crate::TagTrait for BootLoaderNameTag {
fn dst_size(base_tag: &Tag) -> usize {
// The size of the sized portion of the bootloader name tag.
let tag_base_size = 8;
assert!(base_tag.size >= 8);
base_tag.size as usize - tag_base_size
}
}

#[cfg(test)]
mod tests {
use crate::TagType;
use crate::{BootLoaderNameTag, Tag, TagType};

const MSG: &str = "hello";

Expand All @@ -63,12 +79,8 @@ mod tests {
#[test]
fn test_parse_str() {
let tag = get_bytes();
let tag = unsafe {
tag.as_ptr()
.cast::<super::BootLoaderNameTag>()
.as_ref()
.unwrap()
};
let tag = unsafe { &*tag.as_ptr().cast::<Tag>() };
let tag = tag.cast_tag::<BootLoaderNameTag>();
assert_eq!({ tag.typ }, TagType::BootLoaderName);
assert_eq!(tag.name().expect("must be valid UTF-8"), MSG);
}
Expand Down
53 changes: 34 additions & 19 deletions multiboot2/src/command_line.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
//! Module for [CommandLineTag].

use crate::TagTypeId;
use core::mem;
use core::slice;
use crate::{Tag, TagTrait, TagTypeId};
use core::fmt::{Debug, Formatter};
use core::str;

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

impl CommandLineTag {
/// Read the command line string that is being passed to the booting kernel.
/// This is an null-terminated UTF-8 string. If this returns `Err` then perhaps the memory
/// is invalid or the bootloader doesn't follow the spec.
/// Reads the command line of the kernel as Rust string slice without
/// the null-byte.
///
/// For example, this returns `"console=ttyS0"`.if the GRUB config
/// contains `"multiboot2 /mykernel console=ttyS0"`.
///
/// If the function returns `Err` then perhaps the memory is invalid.
///
/// # Examples
///
Expand All @@ -33,16 +36,32 @@ impl CommandLineTag {
/// }
/// ```
pub fn command_line(&self) -> Result<&str, str::Utf8Error> {
// strlen without null byte
let strlen = self.size as usize - mem::size_of::<CommandLineTag>();
let bytes = unsafe { slice::from_raw_parts((&self.string) as *const u8, strlen) };
str::from_utf8(bytes)
Tag::get_dst_str_slice(&self.cmdline)
}
}

impl Debug for CommandLineTag {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CommandLineTag")
.field("typ", &{ self.typ })
.field("size", &{ self.size })
.field("cmdline", &self.command_line())
.finish()
}
}

impl TagTrait for CommandLineTag {
fn dst_size(base_tag: &Tag) -> usize {
// The size of the sized portion of the command line tag.
let tag_base_size = 8;
assert!(base_tag.size >= 8);
base_tag.size as usize - tag_base_size
}
}

#[cfg(test)]
mod tests {
use crate::TagType;
use crate::{CommandLineTag, Tag, TagType};

const MSG: &str = "hello";

Expand All @@ -67,12 +86,8 @@ mod tests {
#[test]
fn test_parse_str() {
let tag = get_bytes();
let tag = unsafe {
tag.as_ptr()
.cast::<super::CommandLineTag>()
.as_ref()
.unwrap()
};
let tag = unsafe { &*tag.as_ptr().cast::<Tag>() };
let tag = tag.cast_tag::<CommandLineTag>();
assert_eq!({ tag.typ }, TagType::Cmdline);
assert_eq!(tag.command_line().expect("must be valid UTF-8"), MSG);
}
Expand Down
Loading

0 comments on commit 64904c1

Please sign in to comment.