Skip to content

Commit d055b41

Browse files
authoredSep 21, 2023
Merge pull request #177 from rust-osdev/cstring-follow-up
string parsing followup: introduce better fitting error type + raise MSRV
2 parents 760f0e9 + 62ffedc commit d055b41

File tree

12 files changed

+95
-63
lines changed

12 files changed

+95
-63
lines changed
 

‎.github/workflows/rust.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
name: build (msrv)
2121
uses: ./.github/workflows/_build-rust.yml
2222
with:
23-
rust-version: 1.68.0 # MSRV
23+
rust-version: 1.69.0 # MSRV
2424
do-style-check: false
2525
features: builder
2626

@@ -46,7 +46,7 @@ jobs:
4646
needs: build_msrv
4747
uses: ./.github/workflows/_build-rust.yml
4848
with:
49-
rust-version: 1.68.0 # MSRV
49+
rust-version: 1.69.0 # MSRV
5050
do-style-check: false
5151
rust-target: thumbv7em-none-eabihf
5252
features: builder
@@ -103,7 +103,7 @@ jobs:
103103
needs: build_msrv
104104
uses: ./.github/workflows/_build-rust.yml
105105
with:
106-
rust-version: 1.68.0 # MSRV
106+
rust-version: 1.69.0 # MSRV
107107
do-style-check: true
108108
do-test: false
109109
features: builder

‎Cargo.lock

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

‎integration-test/bins/Cargo.lock

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

‎multiboot2/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Multiboot2-compliant bootloaders, such as GRUB. It supports all tags from the
66
specification including full support for the sections of ELF files. This library
77
is `no_std` and can be used in a Multiboot2-kernel.
88
"""
9-
version = "0.18.1"
9+
version = "0.19.0"
1010
authors = [
1111
"Philipp Oppermann <dev@phil-opp.com>",
1212
"Calvin Lee <cyrus296@gmail.com>",

‎multiboot2/Changelog.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# CHANGELOG for crate `multiboot2`
22

3-
## 0.19.0 (2023-07-14)
3+
## 0.19.0 (2023-09-XX)
4+
- **BREAKING** MSRV is 1.69.0
5+
- **BREAKING** `Tag::get_dst_str_slice` renamed to
6+
`Tag::parse_slice_as_string` and now returns `Result<&str, StringError>`
7+
- **BREAKING** `BootLoaderNameTag::name` now returns `Result<&str, StringError>`
8+
- **BREAKING** `CommandLineTag::cmdline` now returns `Result<&str, StringError>`
9+
- **BREAKING** `ModuleTag::cmdline` now returns `Result<&str, StringError>`
10+
- Introduced new enum type `StringError`
11+
- Additionally, a bug was fixed in `parse_slice_as_string` which now parses
12+
multiboot2 strings as expected: as null-terminated UTF-8 strings.
413
- `InformationBuilder` now also allows to add custom tags. The new public method
514
`add_tag` was introduced for that.
615

‎multiboot2/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ other fields | variable
3838
All tags and the mbi itself are 8-byte aligned. The last tag must be the _end tag_, which is a tag of type `0` and size `8`.
3939

4040
## MSRV
41-
The MSRV is 1.68.0 stable.
41+
The MSRV is 1.69.0 stable.
4242

4343
## License & Contribution
4444

‎multiboot2/src/boot_loader_name.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! Module for [`BootLoaderNameTag`].
22
3+
use crate::tag::StringError;
34
use crate::{Tag, TagTrait, TagType, TagTypeId};
45
use core::fmt::{Debug, Formatter};
56
use core::mem::size_of;
6-
use core::str::Utf8Error;
77
#[cfg(feature = "builder")]
88
use {crate::builder::BoxedDst, alloc::vec::Vec};
99

@@ -47,8 +47,8 @@ impl BootLoaderNameTag {
4747
/// assert_eq!(Ok("GRUB 2.02~beta3-5"), tag.name());
4848
/// }
4949
/// ```
50-
pub fn name(&self) -> Result<&str, Utf8Error> {
51-
Tag::get_dst_str_slice(&self.name)
50+
pub fn name(&self) -> Result<&str, StringError> {
51+
Tag::parse_slice_as_string(&self.name)
5252
}
5353
}
5454

‎multiboot2/src/builder/boxed_dst.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ impl<T: ?Sized + PartialEq> PartialEq for BoxedDst<T> {
103103
#[cfg(test)]
104104
mod tests {
105105
use super::*;
106+
use crate::tag::StringError;
106107
use crate::TagType;
107108

108109
const METADATA_SIZE: usize = 8;
@@ -116,8 +117,8 @@ mod tests {
116117
}
117118

118119
impl CustomTag {
119-
fn string(&self) -> Result<&str, core::str::Utf8Error> {
120-
Tag::get_dst_str_slice(&self.string)
120+
fn string(&self) -> Result<&str, StringError> {
121+
Tag::parse_slice_as_string(&self.string)
121122
}
122123
}
123124

@@ -132,11 +133,12 @@ mod tests {
132133

133134
#[test]
134135
fn test_boxed_dst_tag() {
135-
let content = "hallo";
136+
let content = b"hallo\0";
137+
let content_rust_str = "hallo";
136138

137-
let tag = BoxedDst::<CustomTag>::new(content.as_bytes());
139+
let tag = BoxedDst::<CustomTag>::new(content);
138140
assert_eq!(tag.typ, CustomTag::ID);
139141
assert_eq!(tag.size as usize, METADATA_SIZE + content.len());
140-
assert_eq!(tag.string(), Ok(content));
142+
assert_eq!(tag.string(), Ok(content_rust_str));
141143
}
142144
}

‎multiboot2/src/command_line.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
33
use crate::{Tag, TagTrait, TagType, TagTypeId};
44

5+
use crate::tag::StringError;
56
use core::fmt::{Debug, Formatter};
67
use core::mem;
78
use core::str;
8-
99
#[cfg(feature = "builder")]
1010
use {crate::builder::BoxedDst, alloc::vec::Vec};
1111

@@ -55,8 +55,8 @@ impl CommandLineTag {
5555
/// assert_eq!(Ok("/bootarg"), command_line);
5656
/// }
5757
/// ```
58-
pub fn cmdline(&self) -> Result<&str, str::Utf8Error> {
59-
Tag::get_dst_str_slice(&self.cmdline)
58+
pub fn cmdline(&self) -> Result<&str, StringError> {
59+
Tag::parse_slice_as_string(&self.cmdline)
6060
}
6161
}
6262

‎multiboot2/src/lib.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
//! ```
3434
//!
3535
//! ## MSRV
36-
//! The MSRV is 1.68.0 stable.
36+
//! The MSRV is 1.69.0 stable.
3737
3838
#[cfg(feature = "builder")]
3939
extern crate alloc;
@@ -84,7 +84,7 @@ pub use module::{ModuleIter, ModuleTag};
8484
pub use ptr_meta::Pointee;
8585
pub use rsdp::{RsdpV1Tag, RsdpV2Tag};
8686
pub use smbios::SmbiosTag;
87-
pub use tag::Tag;
87+
pub use tag::{StringError, Tag};
8888
pub use tag_trait::TagTrait;
8989
pub use tag_type::{TagType, TagTypeId};
9090
pub use vbe_info::{
@@ -447,7 +447,7 @@ impl<'a> BootInformation<'a> {
447447
///
448448
/// impl CustomTag {
449449
/// fn name(&self) -> Result<&str, Utf8Error> {
450-
/// Tag::get_dst_str_slice(&self.name)
450+
/// Tag::parse_slice_as_string(&self.name)
451451
/// }
452452
/// }
453453
/// let mbi_ptr = 0xdeadbeef as *const BootInformationHeader;
@@ -524,7 +524,7 @@ impl fmt::Debug for BootInformation<'_> {
524524
mod tests {
525525
use super::*;
526526
use crate::memory_map::MemoryAreaType;
527-
use core::str::Utf8Error;
527+
use crate::tag::StringError;
528528

529529
/// Compile time test to check if the boot information is Send and Sync.
530530
/// This test is relevant to give library users flexebility in passing the
@@ -542,7 +542,6 @@ mod tests {
542542
8, 0, 0, 0, // end tag size
543543
]);
544544
let ptr = bytes.0.as_ptr();
545-
let addr = ptr as usize;
546545
let bi = unsafe { BootInformation::load(ptr.cast()) };
547546
let bi = bi.unwrap();
548547

@@ -1604,8 +1603,8 @@ mod tests {
16041603
}
16051604

16061605
impl CustomTag {
1607-
fn name(&self) -> Result<&str, Utf8Error> {
1608-
Tag::get_dst_str_slice(&self.name)
1606+
fn name(&self) -> Result<&str, StringError> {
1607+
Tag::parse_slice_as_string(&self.name)
16091608
}
16101609
}
16111610

‎multiboot2/src/module.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
//! Module for [`ModuleTag`].
22
3+
use crate::tag::StringError;
34
use crate::{Tag, TagIter, TagTrait, TagType, TagTypeId};
45
use core::fmt::{Debug, Formatter};
56
use core::mem::size_of;
6-
use core::str::Utf8Error;
7-
87
#[cfg(feature = "builder")]
98
use {crate::builder::BoxedDst, alloc::vec::Vec};
109

@@ -48,8 +47,8 @@ impl ModuleTag {
4847
/// contains `"module2 /some_boot_module --test cmdline-option"`.
4948
///
5049
/// If the function returns `Err` then perhaps the memory is invalid.
51-
pub fn cmdline(&self) -> Result<&str, Utf8Error> {
52-
Tag::get_dst_str_slice(&self.cmdline)
50+
pub fn cmdline(&self) -> Result<&str, StringError> {
51+
Tag::parse_slice_as_string(&self.cmdline)
5352
}
5453

5554
/// Start address of the module.

‎multiboot2/src/tag.rs

+54-31
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,36 @@
44
55
use crate::{TagTrait, TagType, TagTypeId};
66
use core::fmt;
7-
use core::fmt::{Debug, Formatter};
7+
use core::fmt::{Debug, Display, Formatter};
88
use core::marker::PhantomData;
99
use core::str::Utf8Error;
1010

11+
/// Error type describing failures when parsing the string from a tag.
12+
#[derive(Debug, PartialEq, Eq, Clone)]
13+
pub enum StringError {
14+
/// There is no terminating NUL character, although the specification
15+
/// requires one.
16+
MissingNul(core::ffi::FromBytesUntilNulError),
17+
/// The sequence until the first NUL character is not valid UTF-8.
18+
Utf8(Utf8Error),
19+
}
20+
21+
impl Display for StringError {
22+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
23+
write!(f, "{:?}", self)
24+
}
25+
}
26+
27+
#[cfg(feature = "unstable")]
28+
impl core::error::Error for StringError {
29+
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
30+
match self {
31+
StringError::MissingNul(e) => Some(e),
32+
StringError::Utf8(e) => Some(e),
33+
}
34+
}
35+
}
36+
1137
/// Common base structure for all tags that can be passed via the Multiboot2
1238
/// Information Structure (MBI) to a Multiboot2 payload/program/kernel.
1339
///
@@ -38,22 +64,12 @@ impl Tag {
3864
unsafe { TagTrait::from_base_tag(self) }
3965
}
4066

41-
/// Some multiboot2 tags are a DST as they end with a dynamically sized byte
42-
/// slice. This function parses this slice as [`str`] so that either a valid
43-
/// UTF-8 Rust string slice without a terminating null byte or an error is
44-
/// returned.
45-
pub fn get_dst_str_slice(bytes: &[u8]) -> Result<&str, Utf8Error> {
46-
// Determine length of string before first NUL character
47-
48-
let length = match bytes.iter().position(|ch| (*ch) == 0x00) {
49-
// Unterminated string case:
50-
None => bytes.len(),
51-
52-
// Terminated case:
53-
Some(idx) => idx,
54-
};
55-
// Convert to Rust string:
56-
core::str::from_utf8(&bytes[..length])
67+
/// Parses the provided byte sequence as Multiboot string, which maps to a
68+
/// [`str`].
69+
pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> {
70+
let cstr = core::ffi::CStr::from_bytes_until_nul(bytes).map_err(StringError::MissingNul)?;
71+
72+
cstr.to_str().map_err(StringError::Utf8)
5773
}
5874
}
5975

@@ -128,21 +144,28 @@ mod tests {
128144
use super::*;
129145

130146
#[test]
131-
fn test_get_dst_str_slice() {
132-
// unlikely case
133-
assert_eq!(Ok(""), Tag::get_dst_str_slice(&[]));
134-
// also unlikely case
135-
assert_eq!(Ok(""), Tag::get_dst_str_slice(&[b'\0']));
136-
// unlikely case: missing null byte. but the lib can cope with that
137-
assert_eq!(Ok("foobar"), Tag::get_dst_str_slice(b"foobar"));
138-
// test that the null bytes is not included in the string slice
139-
assert_eq!(Ok("foobar"), Tag::get_dst_str_slice(b"foobar\0"));
140-
// test that C-style null string termination works as expected
141-
assert_eq!(Ok("foo"), Tag::get_dst_str_slice(b"foo\0bar"));
142-
// test invalid utf8
147+
fn parse_slice_as_string() {
148+
// empty slice is invalid
149+
assert!(matches!(
150+
Tag::parse_slice_as_string(&[]),
151+
Err(StringError::MissingNul(_))
152+
));
153+
// empty string is fine
154+
assert_eq!(Tag::parse_slice_as_string(&[0x00]), Ok(""));
155+
// reject invalid utf8
156+
assert!(matches!(
157+
Tag::parse_slice_as_string(&[0xff, 0x00]),
158+
Err(StringError::Utf8(_))
159+
));
160+
// reject missing null
143161
assert!(matches!(
144-
Tag::get_dst_str_slice(&[0xff, 0xff]),
145-
Err(Utf8Error { .. })
162+
Tag::parse_slice_as_string(b"hello"),
163+
Err(StringError::MissingNul(_))
146164
));
165+
// must not include final null
166+
assert_eq!(Tag::parse_slice_as_string(b"hello\0"), Ok("hello"));
167+
assert_eq!(Tag::parse_slice_as_string(b"hello\0\0"), Ok("hello"));
168+
// must skip everytihng after first null
169+
assert_eq!(Tag::parse_slice_as_string(b"hello\0foo"), Ok("hello"));
147170
}
148171
}

0 commit comments

Comments
 (0)
Please sign in to comment.