diff --git a/multiboot2/Changelog.md b/multiboot2/Changelog.md index 8b1db759..4565f755 100644 --- a/multiboot2/Changelog.md +++ b/multiboot2/Changelog.md @@ -1,5 +1,9 @@ # CHANGELOG for crate `multiboot2` +## 0.19.0 (2023-07-14) +- `InformationBuilder` now also allows to add custom tags. The new public method + `add_tag` was introduced for that. + ## 0.18.1 (2023-07-13) - Documentation improvements diff --git a/multiboot2/src/builder/information.rs b/multiboot2/src/builder/information.rs index 221cd91f..7b6fe990 100644 --- a/multiboot2/src/builder/information.rs +++ b/multiboot2/src/builder/information.rs @@ -7,6 +7,7 @@ use crate::{ MemoryMapTag, ModuleTag, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagTrait, TagType, }; use alloc::vec::Vec; +use core::fmt::{Display, Formatter}; use core::mem::size_of; use core::ops::Deref; @@ -41,142 +42,71 @@ impl Deref for BootInformationBytes { } } +type SerializedTag = Vec; + +/// Error that indicates a tag was added multiple times that is not allowed to +/// be there multiple times. +#[derive(Debug)] +pub struct RedundantTagError(TagType); + +impl Display for RedundantTagError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "unstable")] +impl core::error::Error for RedundantTagError {} + /// Builder to construct a valid Multiboot2 information dynamically at runtime. /// The tags will appear in the order of their corresponding enumeration, /// except for the END tag. #[derive(Debug, PartialEq, Eq)] -pub struct InformationBuilder { - basic_memory_info_tag: Option, - boot_loader_name_tag: Option>, - command_line_tag: Option>, - efi_boot_services_not_exited_tag: Option, - efi_image_handle32: Option, - efi_image_handle64: Option, - efi_memory_map_tag: Option>, - elf_sections_tag: Option>, - framebuffer_tag: Option>, - image_load_addr: Option, - memory_map_tag: Option>, - module_tags: Vec>, - efisdt32_tag: Option, - efisdt64_tag: Option, - rsdp_v1_tag: Option, - rsdp_v2_tag: Option, - smbios_tags: Vec>, -} +pub struct InformationBuilder(Vec<(TagType, SerializedTag)>); impl InformationBuilder { + /// Creates a new builder. pub const fn new() -> Self { - Self { - basic_memory_info_tag: None, - boot_loader_name_tag: None, - command_line_tag: None, - efisdt32_tag: None, - efisdt64_tag: None, - efi_boot_services_not_exited_tag: None, - efi_image_handle32: None, - efi_image_handle64: None, - efi_memory_map_tag: None, - elf_sections_tag: None, - framebuffer_tag: None, - image_load_addr: None, - memory_map_tag: None, - module_tags: Vec::new(), - rsdp_v1_tag: None, - rsdp_v2_tag: None, - smbios_tags: Vec::new(), - } + Self(Vec::new()) } - /// Returns the size, if the value is a multiple of 8 or returns - /// the next number that is a multiple of 8. With this, one can - /// easily calculate the size of a Multiboot2 header, where - /// all the tags are 8-byte aligned. + /// Returns the provided number or the next multiple of 8. This is helpful + /// to ensure that the following tag starts at a 8-byte aligned boundary. const fn size_or_up_aligned(size: usize) -> usize { (size + 7) & !7 } /// Returns the expected length of the boot information, when the - /// [`Self::build`]-method gets called. + /// [`Self::build`]-method is called. This function assumes that the begin + /// of the boot information is 8-byte aligned and automatically adds padding + /// between tags to ensure that each tag is 8-byte aligned. pub fn expected_len(&self) -> usize { - let base_len = size_of::(); - // size_or_up_aligned not required, because length is 16 and the - // begin is 8 byte aligned => first tag automatically 8 byte aligned - let mut len = Self::size_or_up_aligned(base_len); - if let Some(tag) = &self.basic_memory_info_tag { - // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.boot_loader_name_tag { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.command_line_tag { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.efisdt32_tag { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.efisdt64_tag { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.efi_boot_services_not_exited_tag { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.efi_image_handle32 { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.efi_image_handle64 { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.efi_memory_map_tag { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.elf_sections_tag { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.framebuffer_tag { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.image_load_addr { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.memory_map_tag { - len += Self::size_or_up_aligned(tag.size()) - } - for tag in &self.module_tags { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.rsdp_v1_tag { - len += Self::size_or_up_aligned(tag.size()) - } - if let Some(tag) = &self.rsdp_v2_tag { - len += Self::size_or_up_aligned(tag.size()) - } - for tag in &self.smbios_tags { - len += Self::size_or_up_aligned(tag.size()) - } - // only here size_or_up_aligned is not important, because it is the last tag - len += size_of::(); - len + let tag_size_iter = self.0.iter().map(|(_, bytes)| bytes.len()); + + let payload_tags_size = tag_size_iter.fold(0, |acc, tag_size| { + // size_or_up_aligned: make sure next tag is 8-byte aligned + acc + Self::size_or_up_aligned(tag_size) + }); + + size_of::() + payload_tags_size + size_of::() } /// Adds the bytes of a tag to the final Multiboot2 information byte vector. - fn build_add_tag(dest: &mut Vec, source: &T) { - let vec_next_write_ptr = unsafe { dest.as_ptr().add(dest.len()) }; + fn build_add_tag(dest_buf: &mut Vec, tag_serialized: &[u8], tag_type: TagType) { + let vec_next_write_ptr = unsafe { dest_buf.as_ptr().add(dest_buf.len()) }; + // At this point, the alignment is guaranteed. If not, something is // broken fundamentally. assert_eq!(vec_next_write_ptr.align_offset(8), 0); - dest.extend(source.as_bytes()); - - let is_end_tag = source.as_base_tag().typ == TagType::End; + dest_buf.extend(tag_serialized); - if !is_end_tag { - let size = source.size(); + if tag_type != TagType::End { + let size = tag_serialized.len(); let size_to_8_align = Self::size_or_up_aligned(size); let size_to_8_align_diff = size_to_8_align - size; // fill zeroes so that next data block is 8-byte aligned - dest.extend([0].repeat(size_to_8_align_diff)); + dest_buf.extend([0].repeat(size_to_8_align_diff)); } } @@ -207,7 +137,11 @@ impl InformationBuilder { // ----------------------------------------------- // PHASE 2/2: Add Tags bytes.extend(BootInformationHeader::new(self.expected_len() as u32).as_bytes()); - self.build_add_tags(&mut bytes); + + for (tag_type, tag_serialized) in self.0 { + Self::build_add_tag(&mut bytes, tag_serialized.as_slice(), tag_type) + } + Self::build_add_tag(&mut bytes, EndTag::default().as_bytes(), TagType::End); assert_eq!( alloc_ptr, @@ -227,166 +161,127 @@ impl InformationBuilder { } } - /// Helper method that adds all the tags to the given vector. - fn build_add_tags(&self, bytes: &mut Vec) { - if let Some(tag) = self.basic_memory_info_tag.as_ref() { - Self::build_add_tag(bytes, tag) - } - if let Some(tag) = self.boot_loader_name_tag.as_ref() { - Self::build_add_tag(bytes, &**tag) - } - if let Some(tag) = self.command_line_tag.as_ref() { - Self::build_add_tag(bytes, &**tag) - } - if let Some(tag) = self.efisdt32_tag.as_ref() { - Self::build_add_tag(bytes, tag) - } - if let Some(tag) = self.efisdt64_tag.as_ref() { - Self::build_add_tag(bytes, tag) - } - if let Some(tag) = self.efi_boot_services_not_exited_tag.as_ref() { - Self::build_add_tag(bytes, tag) - } - if let Some(tag) = self.efi_image_handle32.as_ref() { - Self::build_add_tag(bytes, tag) - } - if let Some(tag) = self.efi_image_handle64.as_ref() { - Self::build_add_tag(bytes, tag) - } - if let Some(tag) = self.efi_memory_map_tag.as_ref() { - Self::build_add_tag(bytes, &**tag) - } - if let Some(tag) = self.elf_sections_tag.as_ref() { - Self::build_add_tag(bytes, &**tag) - } - if let Some(tag) = self.framebuffer_tag.as_ref() { - Self::build_add_tag(bytes, &**tag) - } - if let Some(tag) = self.image_load_addr.as_ref() { - Self::build_add_tag(bytes, tag) - } - if let Some(tag) = self.memory_map_tag.as_ref() { - Self::build_add_tag(bytes, &**tag) - } - for tag in &self.module_tags { - Self::build_add_tag(bytes, &**tag) - } - if let Some(tag) = self.rsdp_v1_tag.as_ref() { - Self::build_add_tag(bytes, tag) - } - if let Some(tag) = self.rsdp_v2_tag.as_ref() { - Self::build_add_tag(bytes, tag) - } - for tag in &self.smbios_tags { - Self::build_add_tag(bytes, &**tag) - } - Self::build_add_tag(bytes, &EndTag::default()); + /// Adds a arbitrary tag that implements [`TagTrait`] to the builder. Only + /// [`TagType::Module`] and [`TagType::Custom`] are allowed to occur + /// multiple times. For other tags, this function returns an error. + /// + /// It is not required to manually add the [`TagType::End`] tag. + /// + /// The tags of the boot information will be ordered naturally, i.e., by + /// their numeric ID. + pub fn add_tag(mut self, tag: &T) -> Result { + // not required to do this manually + if T::ID == TagType::End { + return Ok(self); + } + + let is_redundant_tag = self + .0 + .iter() + .map(|(typ, _)| *typ) + .any(|typ| typ == T::ID && !Self::tag_is_allowed_multiple_times(typ)); + + if is_redundant_tag { + log::debug!( + "Can't add tag of type {:?}. Only Module tags and Custom tags are allowed to appear multiple times.", + T::ID + ); + return Err(RedundantTagError(T::ID)); + } + self.0.push((T::ID, tag.as_bytes().to_vec())); + self.0.sort_by_key(|(typ, _)| *typ); + + Ok(self) } /// Adds a 'basic memory information' tag (represented by [`BasicMemoryInfoTag`]) to the builder. - pub fn basic_memory_info_tag(mut self, basic_memory_info_tag: BasicMemoryInfoTag) -> Self { - self.basic_memory_info_tag = Some(basic_memory_info_tag); - self + pub fn basic_memory_info_tag(self, tag: BasicMemoryInfoTag) -> Self { + self.add_tag(&tag).unwrap() } /// Adds a 'bootloader name' tag (represented by [`BootLoaderNameTag`]) to the builder. - pub fn bootloader_name_tag( - mut self, - boot_loader_name_tag: BoxedDst, - ) -> Self { - self.boot_loader_name_tag = Some(boot_loader_name_tag); - self + pub fn bootloader_name_tag(self, tag: BoxedDst) -> Self { + self.add_tag(&*tag).unwrap() } /// Adds a 'command line' tag (represented by [`CommandLineTag`]) to the builder. - pub fn command_line_tag(mut self, command_line_tag: BoxedDst) -> Self { - self.command_line_tag = Some(command_line_tag); - self + pub fn command_line_tag(self, tag: BoxedDst) -> Self { + self.add_tag(&*tag).unwrap() } /// Adds a 'EFI 32-bit system table pointer' tag (represented by [`EFISdt32Tag`]) to the builder. - pub fn efisdt32_tag(mut self, efisdt32: EFISdt32Tag) -> Self { - self.efisdt32_tag = Some(efisdt32); - self + pub fn efisdt32_tag(self, tag: EFISdt32Tag) -> Self { + self.add_tag(&tag).unwrap() } /// Adds a 'EFI 64-bit system table pointer' tag (represented by [`EFISdt64Tag`]) to the builder. - pub fn efisdt64_tag(mut self, efisdt64: EFISdt64Tag) -> Self { - self.efisdt64_tag = Some(efisdt64); - self + pub fn efisdt64_tag(self, tag: EFISdt64Tag) -> Self { + self.add_tag(&tag).unwrap() } /// Adds a 'EFI boot services not terminated' tag (represented by [`EFIBootServicesNotExitedTag`]) to the builder. - pub fn efi_boot_services_not_exited_tag(mut self) -> Self { - self.efi_boot_services_not_exited_tag = Some(EFIBootServicesNotExitedTag::new()); - self + pub fn efi_boot_services_not_exited_tag(self) -> Self { + self.add_tag(&EFIBootServicesNotExitedTag::new()).unwrap() } /// Adds a 'EFI 32-bit image handle pointer' tag (represented by [`EFIImageHandle32Tag`]) to the builder. - pub fn efi_image_handle32(mut self, efi_image_handle32: EFIImageHandle32Tag) -> Self { - self.efi_image_handle32 = Some(efi_image_handle32); - self + pub fn efi_image_handle32(self, tag: EFIImageHandle32Tag) -> Self { + self.add_tag(&tag).unwrap() } /// Adds a 'EFI 64-bit image handle pointer' tag (represented by [`EFIImageHandle64Tag`]) to the builder. - pub fn efi_image_handle64(mut self, efi_image_handle64: EFIImageHandle64Tag) -> Self { - self.efi_image_handle64 = Some(efi_image_handle64); - self + pub fn efi_image_handle64(self, tag: EFIImageHandle64Tag) -> Self { + self.add_tag(&tag).unwrap() } /// Adds a 'EFI Memory map' tag (represented by [`EFIMemoryMapTag`]) to the builder. - pub fn efi_memory_map_tag(mut self, efi_memory_map_tag: BoxedDst) -> Self { - self.efi_memory_map_tag = Some(efi_memory_map_tag); - self + pub fn efi_memory_map_tag(self, tag: BoxedDst) -> Self { + self.add_tag(&*tag).unwrap() } /// Adds a 'ELF-Symbols' tag (represented by [`ElfSectionsTag`]) to the builder. - pub fn elf_sections_tag(mut self, elf_sections_tag: BoxedDst) -> Self { - self.elf_sections_tag = Some(elf_sections_tag); - self + pub fn elf_sections_tag(self, tag: BoxedDst) -> Self { + self.add_tag(&*tag).unwrap() } /// Adds a 'Framebuffer info' tag (represented by [`FramebufferTag`]) to the builder. - pub fn framebuffer_tag(mut self, framebuffer_tag: BoxedDst) -> Self { - self.framebuffer_tag = Some(framebuffer_tag); - self + pub fn framebuffer_tag(self, tag: BoxedDst) -> Self { + self.add_tag(&*tag).unwrap() } /// Adds a 'Image load base physical address' tag (represented by [`ImageLoadPhysAddrTag`]) to the builder. - pub fn image_load_addr(mut self, image_load_addr: ImageLoadPhysAddrTag) -> Self { - self.image_load_addr = Some(image_load_addr); - self + pub fn image_load_addr(self, tag: ImageLoadPhysAddrTag) -> Self { + self.add_tag(&tag).unwrap() } /// Adds a (*none EFI*) 'memory map' tag (represented by [`MemoryMapTag`]) to the builder. - pub fn memory_map_tag(mut self, memory_map_tag: BoxedDst) -> Self { - self.memory_map_tag = Some(memory_map_tag); - self + pub fn memory_map_tag(self, tag: BoxedDst) -> Self { + self.add_tag(&*tag).unwrap() } /// Adds a 'Modules' tag (represented by [`ModuleTag`]) to the builder. /// This tag can occur multiple times in boot information. - pub fn add_module_tag(mut self, module_tag: BoxedDst) -> Self { - self.module_tags.push(module_tag); - self + pub fn add_module_tag(self, tag: BoxedDst) -> Self { + self.add_tag(&*tag).unwrap() } /// Adds a 'ACPI old RSDP' tag (represented by [`RsdpV1Tag`]) to the builder. - pub fn rsdp_v1_tag(mut self, rsdp_v1_tag: RsdpV1Tag) -> Self { - self.rsdp_v1_tag = Some(rsdp_v1_tag); - self + pub fn rsdp_v1_tag(self, tag: RsdpV1Tag) -> Self { + self.add_tag(&tag).unwrap() } /// Adds a 'ACPI new RSDP' tag (represented by [`RsdpV2Tag`]) to the builder. - pub fn rsdp_v2_tag(mut self, rsdp_v2_tag: RsdpV2Tag) -> Self { - self.rsdp_v2_tag = Some(rsdp_v2_tag); - self + pub fn rsdp_v2_tag(self, tag: RsdpV2Tag) -> Self { + self.add_tag(&tag).unwrap() } /// Adds a 'SMBIOS tables' tag (represented by [`SmbiosTag`]) to the builder. - pub fn add_smbios_tag(mut self, smbios_tag: BoxedDst) -> Self { - self.smbios_tags.push(smbios_tag); - self + pub fn smbios_tag(self, tag: BoxedDst) -> Self { + self.add_tag(&*tag).unwrap() + } + + fn tag_is_allowed_multiple_times(tag_type: TagType) -> bool { + matches!(tag_type, TagType::Module | TagType::Custom(_)) } }