Skip to content

multiboot2: properly type DST tags #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-header/src/information_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub struct InformationRequestHeaderTagIter<'a> {

impl<'a> InformationRequestHeaderTagIter<'a> {
fn new(count: u32, base_ptr: *const MbiTagType) -> Self {
#[allow(clippy::default_constructed_unit_structs)]
Self {
i: 0,
count,
Expand Down
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
57 changes: 35 additions & 22 deletions multiboot2/src/boot_loader_name.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
use crate::TagTypeId;
use crate::TagTrait;
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 +30,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 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 +80,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