diff --git a/src/data_types/chars.rs b/src/data_types/chars.rs new file mode 100644 index 000000000..08728cf31 --- /dev/null +++ b/src/data_types/chars.rs @@ -0,0 +1,128 @@ +//! UEFI character handling +//! +//! UEFI uses both Latin-1 and UCS-2 character encoding, this module implements +//! support for the associated character types. + +use core::convert::{TryFrom, TryInto}; +use core::fmt; + +/// Character conversion error +pub struct CharConversionError; + +/// A Latin-1 character +#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Char8(u8); + +impl TryFrom for Char8 { + type Error = CharConversionError; + + fn try_from(value: char) -> Result { + let code_point = value as u32; + if code_point <= 0xff { + Ok(Char8(code_point as u8)) + } else { + Err(CharConversionError) + } + } +} + +impl Into for Char8 { + fn into(self) -> char { + self.0 as char + } +} + +impl From for Char8 { + fn from(value: u8) -> Self { + Char8(value) + } +} + +impl Into for Char8 { + fn into(self) -> u8 { + self.0 as u8 + } +} + +impl fmt::Debug for Char8 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(&From::from(self.0), f) + } +} + +impl fmt::Display for Char8 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(&From::from(self.0), f) + } +} + +/// Latin-1 version of the NUL character +pub const NUL_8: Char8 = Char8(0); + +/// An UCS-2 code point +#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Char16(u16); + +impl TryFrom for Char16 { + type Error = CharConversionError; + + fn try_from(value: char) -> Result { + let code_point = value as u32; + if code_point <= 0xffff { + Ok(Char16(code_point as u16)) + } else { + Err(CharConversionError) + } + } +} + +impl Into for Char16 { + fn into(self) -> char { + u32::from(self.0).try_into().unwrap() + } +} + +impl TryFrom for Char16 { + type Error = CharConversionError; + + fn try_from(value: u16) -> Result { + // We leverage char's TryFrom impl for Unicode validity checking + let res: Result = u32::from(value).try_into(); + if let Ok(ch) = res { + ch.try_into() + } else { + Err(CharConversionError) + } + } +} + +impl Into for Char16 { + fn into(self) -> u16 { + self.0 as u16 + } +} + +impl fmt::Debug for Char16 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Ok(c) = u32::from(self.0).try_into() { + ::fmt(&c, f) + } else { + write!(f, "Char16({:?})", self.0) + } + } +} + +impl fmt::Display for Char16 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Ok(c) = u32::from(self.0).try_into() { + ::fmt(&c, f) + } else { + write!(f, "{}", core::char::REPLACEMENT_CHARACTER) + } + } +} + +/// UCS-2 version of the NUL character +pub const NUL_16: Char16 = Char16(0); diff --git a/src/data_types/mod.rs b/src/data_types/mod.rs index 8f979a90f..d567fa33d 100644 --- a/src/data_types/mod.rs +++ b/src/data_types/mod.rs @@ -1,3 +1,7 @@ +//! Data type definitions +//! +//! This module defines the basic data types that are used throughout uefi-rs + use core::ffi::c_void; /// Opaque handle to an UEFI entity (protocol, image...) @@ -13,5 +17,11 @@ pub struct Event(*mut c_void); mod guid; pub use self::guid::Guid; +pub mod chars; +pub use self::chars::{Char16, Char8}; + #[macro_use] mod enums; + +mod strs; +pub use self::strs::{CStr16, CStr8}; diff --git a/src/data_types/strs.rs b/src/data_types/strs.rs new file mode 100644 index 000000000..d27bd4fa2 --- /dev/null +++ b/src/data_types/strs.rs @@ -0,0 +1,134 @@ +use super::chars::{Char16, Char8, NUL_16, NUL_8}; +use core::convert::TryInto; +use core::result::Result; +use core::slice; + +/// Errors which can occur during checked [uN] -> CStrN conversions +pub enum FromSliceWithNulError { + /// An invalid character was encountered before the end of the slice + InvalidChar(usize), + + /// A null character was encountered before the end of the slice + InteriorNul(usize), + + /// The slice was not null-terminated + NotNulTerminated, +} + +/// A Latin-1 null-terminated string +/// +/// This type is largely inspired by std::ffi::CStr, see documentation of CStr +/// for more details on its semantics. +/// +#[repr(transparent)] +pub struct CStr8([Char8]); + +impl CStr8 { + /// Wraps a raw UEFI string with a safe C string wrapper + pub unsafe fn from_ptr<'a>(ptr: *const Char8) -> &'a Self { + let mut len = 0; + while *ptr.add(len) != NUL_8 { + len += 1 + } + let ptr = ptr as *const u8; + Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1)) + } + + /// Creates a C string wrapper from bytes + pub fn from_bytes_with_nul(chars: &[u8]) -> Result<&Self, FromSliceWithNulError> { + let nul_pos = chars.iter().position(|&c| c == 0); + if let Some(nul_pos) = nul_pos { + if nul_pos + 1 != chars.len() { + return Err(FromSliceWithNulError::InteriorNul(nul_pos)); + } + Ok(unsafe { Self::from_bytes_with_nul_unchecked(chars) }) + } else { + Err(FromSliceWithNulError::NotNulTerminated) + } + } + + /// Unsafely creates a C string wrapper from bytes + pub unsafe fn from_bytes_with_nul_unchecked(chars: &[u8]) -> &Self { + &*(chars as *const [u8] as *const Self) + } + + /// Returns the inner pointer to this C string + pub fn as_ptr(&self) -> *const Char8 { + self.0.as_ptr() + } + + /// Converts this C string to a slice of bytes + pub fn to_bytes(&self) -> &[u8] { + let chars = self.to_bytes_with_nul(); + &chars[..chars.len() - 1] + } + + /// Converts this C string to a slice of bytes containing the trailing 0 char + pub fn to_bytes_with_nul(&self) -> &[u8] { + unsafe { &*(&self.0 as *const [Char8] as *const [u8]) } + } +} + +/// An UCS-2 null-terminated string +/// +/// This type is largely inspired by std::ffi::CStr, see documentation of CStr +/// for more details on its semantics. +/// +#[repr(transparent)] +pub struct CStr16([Char16]); + +impl CStr16 { + /// Wraps a raw UEFI string with a safe C string wrapper + pub unsafe fn from_ptr<'a>(ptr: *const Char16) -> &'a Self { + let mut len = 0; + while *ptr.add(len) != NUL_16 { + len += 1 + } + let ptr = ptr as *const u16; + Self::from_u16_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1)) + } + + /// Creates a C string wrapper from a u16 slice + /// + /// Since not every u16 value is a valid UCS-2 code point, this function + /// must do a bit more validity checking than CStr::from_bytes_with_nul + pub fn from_u16_with_nul(codes: &[u16]) -> Result<&Self, FromSliceWithNulError> { + for (pos, &code) in codes.iter().enumerate() { + match code.try_into() { + Ok(NUL_16) => { + if pos != codes.len() - 1 { + return Err(FromSliceWithNulError::InteriorNul(pos)); + } else { + return Ok(unsafe { Self::from_u16_with_nul_unchecked(codes) }); + } + } + Err(_) => { + return Err(FromSliceWithNulError::InvalidChar(pos)); + } + _ => {} + } + } + Err(FromSliceWithNulError::NotNulTerminated) + } + + /// Unsafely creates a C string wrapper from a u16 slice. + pub unsafe fn from_u16_with_nul_unchecked(codes: &[u16]) -> &Self { + &*(codes as *const [u16] as *const Self) + } + + /// Returns the inner pointer to this C string + pub fn as_ptr(&self) -> *const Char16 { + self.0.as_ptr() + } + + /// Converts this C string to a u16 slice + pub fn to_u16_slice(&self) -> &[u16] { + let chars = self.to_u16_slice_with_nul(); + &chars[..chars.len() - 1] + } + + /// Converts this C string to a u16 slice containing the trailing 0 char + pub fn to_u16_slice_with_nul(&self) -> &[u16] { + unsafe { &*(&self.0 as *const [Char16] as *const [u16]) } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6d1987982..6e82987e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ //! therefore all the network protocols will be unavailable. #![feature(optin_builtin_traits)] +#![feature(try_from)] #![feature(try_trait)] #![no_std] // Enable some additional warnings and lints. @@ -31,8 +32,8 @@ #![deny(clippy::all)] #[macro_use] -mod data_types; -pub use self::data_types::{Event, Guid, Handle}; +pub mod data_types; +pub use self::data_types::{CStr16, CStr8, Char16, Char8, Event, Guid, Handle}; mod error; pub use self::error::{Completion, Result, ResultExt, Status}; diff --git a/src/proto/console/text/input.rs b/src/proto/console/text/input.rs index df41e4a8d..8ef388881 100644 --- a/src/proto/console/text/input.rs +++ b/src/proto/console/text/input.rs @@ -1,5 +1,5 @@ use core::mem; -use crate::{Event, Result, Status}; +use crate::{Char16, Event, Result, Status}; /// Interface for text-based input devices. #[repr(C)] @@ -54,7 +54,7 @@ pub struct Key { pub scan_code: ScanCode, /// Associated Unicode character, /// or 0 if not printable. - pub unicode_char: u16, + pub unicode_char: Char16, } newtype_enum! { diff --git a/src/proto/console/text/output.rs b/src/proto/console/text/output.rs index 977c3558d..bf1936cff 100644 --- a/src/proto/console/text/output.rs +++ b/src/proto/console/text/output.rs @@ -1,6 +1,6 @@ use core::fmt; use crate::prelude::*; -use crate::{Completion, Result, Status}; +use crate::{CStr16, Char16, Completion, Result, Status}; /// Interface for text-based output devices. /// @@ -9,8 +9,8 @@ use crate::{Completion, Result, Status}; #[repr(C)] pub struct Output { reset: extern "win64" fn(this: &Output, extended: bool) -> Status, - output_string: extern "win64" fn(this: &Output, string: *const u16) -> Status, - test_string: extern "win64" fn(this: &Output, string: *const u16) -> Status, + output_string: extern "win64" fn(this: &Output, string: *const Char16) -> Status, + test_string: extern "win64" fn(this: &Output, string: *const Char16) -> Status, query_mode: extern "win64" fn(this: &Output, mode: i32, columns: &mut usize, rows: &mut usize) -> Status, set_mode: extern "win64" fn(this: &mut Output, mode: i32) -> Status, @@ -36,8 +36,8 @@ impl Output { } /// Writes a string to the output device. - pub fn output_string(&mut self, string: *const u16) -> Result<()> { - (self.output_string)(self, string).into() + pub fn output_string(&mut self, string: &CStr16) -> Result<()> { + (self.output_string)(self, string.as_ptr()).into() } /// Checks if a string contains only supported characters. @@ -45,8 +45,8 @@ impl Output { /// /// UEFI applications are encouraged to try to print a string even if it contains /// some unsupported characters. - pub fn test_string(&mut self, string: *const u16) -> bool { - match (self.test_string)(self, string) { + pub fn test_string(&mut self, string: &CStr16) -> bool { + match (self.test_string)(self, string.as_ptr()) { Status::SUCCESS => true, _ => false, } @@ -146,9 +146,12 @@ impl fmt::Write for Output { // This closure writes the local buffer to the output and resets the buffer. let mut flush_buffer = |buf: &mut [u16], i: &mut usize| { buf[*i] = 0; + let codes = &buf[..=*i]; *i = 0; - self.output_string(buf.as_ptr()) + let text = CStr16::from_u16_with_nul(codes).map_err(|_| fmt::Error)?; + + self.output_string(text) .warning_as_error() .map_err(|_| fmt::Error) }; diff --git a/src/proto/media/file.rs b/src/proto/media/file.rs index 99e749ef9..fdf2b3072 100644 --- a/src/proto/media/file.rs +++ b/src/proto/media/file.rs @@ -8,7 +8,7 @@ use bitflags::bitflags; use core::mem; use crate::prelude::*; -use crate::{Result, Status}; +use crate::{CStr16, Char16, Result, Status}; use ucs2; /// A file represents an abstraction of some contiguous block of data residing @@ -22,11 +22,9 @@ pub struct File<'a> { } impl<'a> File<'a> { - pub(super) fn new(ptr: usize) -> Self { + pub(super) unsafe fn new(ptr: usize) -> Self { let ptr = ptr as *mut FileImpl; - - let inner = unsafe { &mut *ptr }; - + let inner = &mut *ptr; File { inner } } @@ -62,12 +60,19 @@ impl<'a> File<'a> { let mut buf = [0u16; BUF_SIZE + 1]; let mut ptr = 0usize; - ucs2::encode(filename, &mut buf)?; - (self.inner.open)(self.inner, &mut ptr, buf.as_ptr(), open_mode, attributes).into_with( - || File { - inner: unsafe { &mut *(ptr as *mut FileImpl) }, - }, + let len = ucs2::encode(filename, &mut buf)?; + let filename = unsafe { CStr16::from_u16_with_nul_unchecked(&buf[..=len]) }; + + (self.inner.open)( + self.inner, + &mut ptr, + filename.as_ptr(), + open_mode, + attributes, ) + .into_with(|| File { + inner: unsafe { &mut *(ptr as *mut FileImpl) }, + }) } } @@ -175,7 +180,7 @@ struct FileImpl { open: extern "win64" fn( this: &mut FileImpl, new_handle: &mut usize, - filename: *const u16, + filename: *const Char16, open_mode: FileMode, attributes: FileAttribute, ) -> Status, diff --git a/src/proto/media/fs.rs b/src/proto/media/fs.rs index c86b01688..7aa0a8727 100644 --- a/src/proto/media/fs.rs +++ b/src/proto/media/fs.rs @@ -27,7 +27,7 @@ impl SimpleFileSystem { /// * `uefi::Status::MEDIA_CHANGED` - The device has a different medium in it pub fn open_volume(&mut self) -> Result { let mut ptr = 0usize; - (self.open_volume)(self, &mut ptr).into_with(|| File::new(ptr)) + (self.open_volume)(self, &mut ptr).into_with(|| unsafe { File::new(ptr) }) } } diff --git a/src/table/system.rs b/src/table/system.rs index 494a37672..efd72a36f 100644 --- a/src/table/system.rs +++ b/src/table/system.rs @@ -1,14 +1,14 @@ use super::{cfg, Header, Revision}; use core::slice; use crate::proto::console::text; -use crate::Handle; +use crate::{CStr16, Char16, Handle}; /// The system table entry points for accessing the core UEFI system functionality. #[repr(C)] pub struct SystemTable { header: Header, /// Null-terminated string representing the firmware's vendor. - pub fw_vendor: *const u16, + fw_vendor: *const Char16, /// Revision of the UEFI specification the firmware conforms to. pub fw_revision: Revision, stdin_handle: Handle, @@ -30,6 +30,11 @@ pub struct SystemTable { // This is unsafe, but it's the best solution we have from now. #[allow(clippy::mut_from_ref)] impl SystemTable { + /// Return the firmware vendor string + pub fn firmware_vendor(&self) -> &CStr16 { + unsafe { CStr16::from_ptr(self.fw_vendor) } + } + /// Returns the revision of this table, which is defined to be /// the revision of the UEFI specification implemented by the firmware. pub fn uefi_revision(&self) -> Revision {