fat32: implement FAT32 (read) driver
This commit is contained in:
parent
80e5e72bb7
commit
a08fe6ab1b
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -2647,6 +2647,19 @@ dependencies = [
|
||||
"yggdrasil-abi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ygg_driver_fat32"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytemuck",
|
||||
"device-api",
|
||||
"libk",
|
||||
"libk-util",
|
||||
"log",
|
||||
"yggdrasil-abi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ygg_driver_input"
|
||||
version = "0.1.0"
|
||||
@ -2895,6 +2908,7 @@ dependencies = [
|
||||
"yboot-proto",
|
||||
"ygg_driver_acpi",
|
||||
"ygg_driver_ahci",
|
||||
"ygg_driver_fat32",
|
||||
"ygg_driver_input",
|
||||
"ygg_driver_net_core",
|
||||
"ygg_driver_net_loopback",
|
||||
|
@ -38,6 +38,7 @@ ygg_driver_net_rtl81xx.path = "driver/net/rtl81xx"
|
||||
|
||||
memfs = { path = "driver/fs/memfs" }
|
||||
ext2 = { path = "driver/fs/ext2" }
|
||||
ygg_driver_fat32.path = "driver/fs/fat32"
|
||||
|
||||
log.workspace = true
|
||||
bitflags.workspace = true
|
||||
|
@ -5,7 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use kernel_arch_interface::{
|
||||
cpu::IpiQueue,
|
||||
cpu::{CpuData, IpiQueue},
|
||||
mem::{
|
||||
DeviceMemoryAttributes, KernelTableManager, PhysicalMemoryAllocator, RawDeviceMemoryMapping,
|
||||
},
|
||||
@ -36,17 +36,21 @@ pub struct TaskContextImpl<K: KernelTableManager, PA: PhysicalMemoryAllocator>(
|
||||
|
||||
static DUMMY_INTERRUPT_MASK: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
pub struct DummyCpuData;
|
||||
|
||||
impl CpuData for DummyCpuData {}
|
||||
|
||||
impl Architecture for ArchitectureImpl {
|
||||
type PerCpuData = ();
|
||||
type PerCpuData = DummyCpuData;
|
||||
type CpuFeatures = ();
|
||||
type BreakpointType = u8;
|
||||
const BREAKPOINT_VALUE: Self::BreakpointType = 0x00;
|
||||
|
||||
fn local_cpu() -> *mut Self::PerCpuData {
|
||||
fn local_cpu() -> *mut () {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
unsafe fn set_local_cpu(_cpu: *mut Self::PerCpuData) {
|
||||
unsafe fn set_local_cpu(_cpu: *mut ()) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@ -154,7 +158,7 @@ impl<TA: TableAllocator> ProcessAddressSpaceManager<TA> for ProcessAddressSpaceI
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_address_with_asid(&self) -> u64 {
|
||||
fn as_address_with_asid(&self) -> (u64, u64) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
14
kernel/driver/fs/fat32/Cargo.toml
Normal file
14
kernel/driver/fs/fat32/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "ygg_driver_fat32"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
yggdrasil-abi.workspace = true
|
||||
device-api.workspace = true
|
||||
libk.workspace = true
|
||||
libk-util.workspace = true
|
||||
|
||||
bytemuck.workspace = true
|
||||
log.workspace = true
|
||||
async-trait.workspace = true
|
176
kernel/driver/fs/fat32/src/data.rs
Normal file
176
kernel/driver/fs/fat32/src/data.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use core::fmt;
|
||||
|
||||
use libk_util::{get_le_u16, get_le_u32};
|
||||
|
||||
use crate::FsLayout;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Bpb<'a> {
|
||||
bytes: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Fat32Ebpb<'a> {
|
||||
bytes: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Fat32FsInfo {
|
||||
bytes: [u8; 512],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ClusterNumber(pub u32);
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct FatStr([u8]);
|
||||
|
||||
impl<'a> Bpb<'a> {
|
||||
pub fn from_bytes(bytes: &'a [u8]) -> Self {
|
||||
assert_eq!(bytes.len(), 512);
|
||||
Self { bytes }
|
||||
}
|
||||
|
||||
pub fn bytes_per_sector(&self) -> usize {
|
||||
get_le_u16(&self.bytes[0x0B..]) as usize
|
||||
}
|
||||
|
||||
pub fn sectors_per_cluster(&self) -> usize {
|
||||
self.bytes[0x0D] as usize
|
||||
}
|
||||
|
||||
pub fn reserved_sectors(&self) -> usize {
|
||||
get_le_u16(&self.bytes[0x0E..]) as usize
|
||||
}
|
||||
|
||||
pub fn fat_count(&self) -> usize {
|
||||
self.bytes[0x10] as usize
|
||||
}
|
||||
|
||||
pub fn root_directory_entries(&self) -> usize {
|
||||
get_le_u16(&self.bytes[0x11..]) as usize
|
||||
}
|
||||
|
||||
pub fn total_sectors_16(&self) -> u16 {
|
||||
get_le_u16(&self.bytes[0x13..])
|
||||
}
|
||||
|
||||
pub fn fat_size_16(&self) -> u16 {
|
||||
get_le_u16(&self.bytes[0x16..])
|
||||
}
|
||||
|
||||
pub fn total_sectors(&self) -> u64 {
|
||||
let small = self.total_sectors_16() as u64;
|
||||
if small == 0 {
|
||||
get_le_u32(&self.bytes[0x20..]) as u64
|
||||
} else {
|
||||
small
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Fat32Ebpb<'a> {
|
||||
pub fn from_bytes(bytes: &'a [u8]) -> Self {
|
||||
assert_eq!(bytes.len(), 512);
|
||||
Self { bytes }
|
||||
}
|
||||
|
||||
pub fn sectors_per_fat(&self) -> usize {
|
||||
get_le_u32(&self.bytes[0x24..]) as usize
|
||||
}
|
||||
|
||||
pub fn root_directory_cluster(&self) -> ClusterNumber {
|
||||
ClusterNumber(get_le_u32(&self.bytes[0x2C..]))
|
||||
}
|
||||
|
||||
pub fn fsinfo_sector(&self) -> u64 {
|
||||
get_le_u16(&self.bytes[0x30..]) as u64
|
||||
}
|
||||
|
||||
pub fn volume_label(&self) -> &FatStr {
|
||||
FatStr::new(&self.bytes[0x47..0x52])
|
||||
}
|
||||
}
|
||||
|
||||
impl FatStr {
|
||||
pub fn new(bytes: &[u8]) -> &Self {
|
||||
let len = bytes
|
||||
.iter()
|
||||
.rposition(|&c| c != b' ' && c != 0)
|
||||
.map(|len| len + 1)
|
||||
.unwrap_or(0);
|
||||
unsafe { core::mem::transmute(&bytes[..len]) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_str(str: &str) -> &Self {
|
||||
unsafe { core::mem::transmute(str.trim_end_matches(' ').as_bytes()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
unsafe { core::mem::transmute(self) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn eq_ignore_case(&self, other: &Self) -> bool {
|
||||
self.0.eq_ignore_ascii_case(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FatStr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for &byte in self.0.iter() {
|
||||
write!(f, "{}", byte.to_ascii_uppercase() as char)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FatStr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "\"")?;
|
||||
for &byte in self.0.iter() {
|
||||
write!(f, "{}", byte.to_ascii_uppercase() as char)?;
|
||||
}
|
||||
write!(f, "\"")
|
||||
}
|
||||
}
|
||||
|
||||
impl Fat32FsInfo {
|
||||
const SIGNATURE0: u32 = 0x41615252;
|
||||
const SIGNATURE1: u32 = 0x61417272;
|
||||
|
||||
pub fn from_bytes(bytes: [u8; 512]) -> Self {
|
||||
Self { bytes }
|
||||
}
|
||||
|
||||
pub fn is_valid_fat32(&self) -> bool {
|
||||
let signature0 = &self.bytes[0..4];
|
||||
let signature1 = &self.bytes[484..488];
|
||||
|
||||
signature0 == &Self::SIGNATURE0.to_le_bytes()
|
||||
&& signature1 == &Self::SIGNATURE1.to_le_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClusterNumber {
|
||||
pub fn first_sector(&self, layout: &FsLayout) -> u64 {
|
||||
layout.first_data_sector + (self.0 as u64 - 2) * layout.sectors_per_cluster as u64
|
||||
}
|
||||
}
|
441
kernel/driver/fs/fat32/src/directory.rs
Normal file
441
kernel/driver/fs/fat32/src/directory.rs
Normal file
@ -0,0 +1,441 @@
|
||||
use core::{any::Any, fmt, mem::MaybeUninit};
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use libk::{
|
||||
block,
|
||||
error::Error,
|
||||
vfs::{
|
||||
CommonImpl, CreateInfo, DirectoryImpl, DirectoryOpenPosition, Filename, Metadata, Node,
|
||||
NodeFlags, NodeRef,
|
||||
},
|
||||
};
|
||||
use libk_util::{
|
||||
get_le_u16, get_le_u32,
|
||||
string::{chars_equal_ignore_case, Utf16LeStr},
|
||||
};
|
||||
use yggdrasil_abi::{
|
||||
io::{DirectoryEntry, FileMode, GroupId, UserId},
|
||||
util::FixedString,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
data::{ClusterNumber, FatStr},
|
||||
file::FileNode,
|
||||
Fat32Fs,
|
||||
};
|
||||
|
||||
pub const DIRENT_SIZE: usize = 32;
|
||||
|
||||
const ATTR_READ_ONLY: u8 = 0x01;
|
||||
const ATTR_HIDDEN: u8 = 0x02;
|
||||
const ATTR_SYSTEM: u8 = 0x04;
|
||||
const ATTR_VOLUME_ID: u8 = 0x08;
|
||||
const ATTR_DIRECTORY: u8 = 0x10;
|
||||
const ATTR_ARCHIVE: u8 = 0x20;
|
||||
|
||||
const ATTR_LFN: u8 = ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID;
|
||||
const ATTR_LFN_MASK: u8 =
|
||||
ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID | ATTR_DIRECTORY | ATTR_ARCHIVE;
|
||||
|
||||
const LFN_LAST: u8 = 0x40;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FatDirectoryEntry<'a> {
|
||||
long_filename: Option<&'a Utf16LeStr>,
|
||||
short_filename: ShortFilename<'a>,
|
||||
is_directory: bool,
|
||||
first_cluster: ClusterNumber,
|
||||
size_bytes: u64,
|
||||
}
|
||||
|
||||
pub struct DirectoryNode {
|
||||
pub(crate) fs: Arc<Fat32Fs>,
|
||||
pub(crate) cluster: ClusterNumber,
|
||||
pub(crate) size_bytes: u64,
|
||||
pub(crate) metadata: Metadata,
|
||||
// Will be used when metadata needs to be updated
|
||||
#[allow(unused)]
|
||||
pub(crate) parent: Option<ClusterNumber>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
struct ShortFilename<'a> {
|
||||
filename: &'a FatStr,
|
||||
extension: &'a FatStr,
|
||||
}
|
||||
|
||||
impl DirectoryNode {
|
||||
async fn iterate<F: FnMut(usize, FatDirectoryEntry) -> Result<bool, Error>>(
|
||||
&self,
|
||||
mut predicate: F,
|
||||
start_position: u64,
|
||||
limit: usize,
|
||||
) -> Result<(usize, u64), Error> {
|
||||
assert_eq!(start_position % DIRENT_SIZE as u64, 0);
|
||||
|
||||
let mut lfn_buffer = [0; 512];
|
||||
|
||||
let mut remaining = limit;
|
||||
let mut offset = 0;
|
||||
let mut position = start_position;
|
||||
let entries_per_sector = self.fs.layout.bytes_per_sector / DIRENT_SIZE;
|
||||
let mut end_of_dir = false;
|
||||
let mut entry_count = 0;
|
||||
|
||||
while remaining != 0 && !end_of_dir {
|
||||
let sector_in_cluster = position / self.fs.layout.bytes_per_sector as u64;
|
||||
if sector_in_cluster >= self.fs.layout.sectors_per_cluster as u64 {
|
||||
todo!("TODO: handle multi-cluster directories");
|
||||
}
|
||||
let offset_in_sector = (position % self.fs.layout.bytes_per_sector as u64) as usize;
|
||||
let max_entries = core::cmp::min(
|
||||
limit - offset,
|
||||
entries_per_sector - offset_in_sector / DIRENT_SIZE,
|
||||
);
|
||||
|
||||
self.fs
|
||||
.with_cluster_sector(self.cluster, sector_in_cluster, |buffer| {
|
||||
for i in 0..max_entries {
|
||||
let offset = offset_in_sector + i * DIRENT_SIZE;
|
||||
let entry = &buffer[offset..offset + DIRENT_SIZE];
|
||||
|
||||
if entry[0] == 0 {
|
||||
end_of_dir = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If name[0] == 0xE5, the entry is free
|
||||
if entry[0] == 0xE5 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let attr = entry[11];
|
||||
|
||||
if attr & ATTR_LFN_MASK == ATTR_LFN {
|
||||
// LFN entry
|
||||
let ord = ((entry[0] & !LFN_LAST) as usize - 1) * 2;
|
||||
|
||||
lfn_buffer[ord * 13..ord * 13 + 10].copy_from_slice(&entry[1..11]);
|
||||
lfn_buffer[ord * 13 + 10..ord * 13 + 22]
|
||||
.copy_from_slice(&entry[14..26]);
|
||||
lfn_buffer[ord * 13 + 22..ord * 13 + 26]
|
||||
.copy_from_slice(&entry[28..32]);
|
||||
} else {
|
||||
let mut lfn_length = 0;
|
||||
for i in (0..lfn_buffer.len()).step_by(2) {
|
||||
let word = get_le_u16(&lfn_buffer[i..]);
|
||||
if word == 0 || word == 0xFFFF {
|
||||
lfn_length = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let long_filename = if lfn_length > 0 {
|
||||
Utf16LeStr::from_utf16le(&lfn_buffer[..lfn_length]).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let short_filename = ShortFilename {
|
||||
filename: FatStr::new(&entry[..8]),
|
||||
extension: FatStr::new(&entry[8..11]),
|
||||
};
|
||||
|
||||
let is_directory = match attr & (ATTR_DIRECTORY | ATTR_VOLUME_ID) {
|
||||
0x00 => false,
|
||||
ATTR_DIRECTORY => true,
|
||||
ATTR_VOLUME_ID => continue,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let first_cluster_hi = get_le_u16(&entry[20..]);
|
||||
let first_cluster_lo = get_le_u16(&entry[26..]);
|
||||
let size_bytes = get_le_u32(&entry[28..]) as u64;
|
||||
let first_cluster =
|
||||
((first_cluster_hi as u32) << 16) | (first_cluster_lo as u32);
|
||||
let first_cluster = ClusterNumber(first_cluster);
|
||||
|
||||
let entry = FatDirectoryEntry {
|
||||
long_filename,
|
||||
short_filename,
|
||||
is_directory,
|
||||
first_cluster,
|
||||
size_bytes,
|
||||
};
|
||||
|
||||
if !predicate(entry_count, entry)? {
|
||||
end_of_dir = true;
|
||||
break;
|
||||
}
|
||||
|
||||
entry_count += 1;
|
||||
lfn_buffer.fill(0);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
position += (max_entries * DIRENT_SIZE) as u64;
|
||||
offset += max_entries;
|
||||
remaining -= max_entries;
|
||||
}
|
||||
|
||||
Ok((entry_count, position))
|
||||
}
|
||||
|
||||
async fn read_entries_inner(
|
||||
&self,
|
||||
position: u64,
|
||||
buffer: &mut [MaybeUninit<DirectoryEntry>],
|
||||
) -> Result<(usize, u64), Error> {
|
||||
if position >= self.size_bytes {
|
||||
return Ok((0, position));
|
||||
}
|
||||
|
||||
let limit = core::cmp::min(
|
||||
((self.size_bytes - position) / DIRENT_SIZE as u64) as usize,
|
||||
buffer.len(),
|
||||
);
|
||||
let (count, position) = self
|
||||
.iterate(
|
||||
|index, entry| {
|
||||
let mut name = FixedString::empty();
|
||||
if let Some(lfn) = entry.long_filename {
|
||||
name.append_from_chars(lfn.chars()).unwrap();
|
||||
} else {
|
||||
name.append_from_bytes(entry.short_filename.filename.as_bytes());
|
||||
if !entry.short_filename.extension.is_empty() {
|
||||
name.append_from_bytes(b".");
|
||||
name.append_from_bytes(entry.short_filename.extension.as_bytes());
|
||||
}
|
||||
}
|
||||
let dirent = DirectoryEntry { ty: None, name };
|
||||
buffer[index].write(dirent);
|
||||
Ok(true)
|
||||
},
|
||||
position,
|
||||
limit,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(position % DIRENT_SIZE as u64, 0);
|
||||
|
||||
Ok((count, position))
|
||||
}
|
||||
|
||||
async fn lookup_inner(&self, name: &Filename) -> Result<NodeRef, Error> {
|
||||
let mut found = None;
|
||||
|
||||
self.iterate(
|
||||
|_, entry| {
|
||||
if entry.name_equals(name) {
|
||||
found = Some((entry.is_directory, entry.first_cluster, entry.size_bytes));
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
},
|
||||
0,
|
||||
(self.size_bytes / DIRENT_SIZE as u64) as usize,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (is_directory, cluster, size) = found.ok_or(Error::DoesNotExist)?;
|
||||
|
||||
let metadata = Metadata {
|
||||
uid: UserId::root(),
|
||||
gid: GroupId::root(),
|
||||
mode: if is_directory {
|
||||
FileMode::default_dir()
|
||||
} else {
|
||||
FileMode::default_file()
|
||||
},
|
||||
inode: Some(cluster.0),
|
||||
ctime: 0,
|
||||
mtime: 0,
|
||||
block_count: size.div_ceil(self.fs.layout.bytes_per_sector as u64),
|
||||
block_size: self.fs.layout.bytes_per_sector as u64,
|
||||
};
|
||||
|
||||
if is_directory {
|
||||
let directory = DirectoryNode {
|
||||
fs: self.fs.clone(),
|
||||
cluster,
|
||||
size_bytes: size,
|
||||
parent: Some(self.cluster),
|
||||
metadata,
|
||||
};
|
||||
|
||||
Ok(Node::directory(
|
||||
directory,
|
||||
NodeFlags::empty(),
|
||||
Some(metadata),
|
||||
Some(self.fs.clone()),
|
||||
))
|
||||
} else {
|
||||
let file = FileNode {
|
||||
fs: self.fs.clone(),
|
||||
cluster,
|
||||
size_bytes: size,
|
||||
parent: Some(self.cluster),
|
||||
metadata,
|
||||
};
|
||||
|
||||
Ok(Node::regular(
|
||||
file,
|
||||
NodeFlags::empty(),
|
||||
Some(metadata),
|
||||
Some(self.fs.clone()),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonImpl for DirectoryNode {
|
||||
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
||||
Ok(self.size_bytes)
|
||||
}
|
||||
|
||||
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
|
||||
Ok(self.metadata.clone())
|
||||
}
|
||||
|
||||
fn set_metadata(&self, _node: &NodeRef, _metadata: &Metadata) -> Result<(), Error> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectoryImpl for DirectoryNode {
|
||||
fn create_node(&self, _parent: &NodeRef, _info: &CreateInfo) -> Result<NodeRef, Error> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
|
||||
fn attach_node(
|
||||
&self,
|
||||
_parent: &NodeRef,
|
||||
_child: &NodeRef,
|
||||
_name: &Filename,
|
||||
) -> Result<(), Error> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
|
||||
fn unlink_node(
|
||||
&self,
|
||||
_parent: &NodeRef,
|
||||
_child: &NodeRef,
|
||||
_name: &Filename,
|
||||
) -> Result<(), Error> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
|
||||
fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
|
||||
Ok(DirectoryOpenPosition::FromPhysical(0))
|
||||
}
|
||||
|
||||
fn lookup(&self, _node: &NodeRef, name: &Filename) -> Result<NodeRef, Error> {
|
||||
// TODO validate FAT32 filename
|
||||
block!(self.lookup_inner(name).await)?
|
||||
}
|
||||
|
||||
fn read_entries(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
pos: u64,
|
||||
entries: &mut [MaybeUninit<DirectoryEntry>],
|
||||
) -> Result<(usize, u64), Error> {
|
||||
block!(self.read_entries_inner(pos, entries).await)?
|
||||
}
|
||||
|
||||
fn len(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
Ok(self.size_bytes as usize / DIRENT_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl FatDirectoryEntry<'_> {
|
||||
pub fn name_equals(&self, predicate: &str) -> bool {
|
||||
if let Some(lfn) = self.long_filename {
|
||||
// Match by LFN
|
||||
chars_equal_ignore_case(lfn.chars(), predicate.chars())
|
||||
} else {
|
||||
// Match by SFN
|
||||
let Some(predicate) = ShortFilename::from_str(predicate) else {
|
||||
return false;
|
||||
};
|
||||
predicate.eq_ignore_case(&self.short_filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ShortFilename<'a> {
|
||||
pub fn from_str(s: &'a str) -> Option<Self> {
|
||||
let (filename, extension) = match s.split_once('.') {
|
||||
Some(xs) => xs,
|
||||
None => (s, ""),
|
||||
};
|
||||
let filename = FatStr::from_str(filename);
|
||||
let extension = FatStr::from_str(extension);
|
||||
if filename.is_empty() || filename.len() > 8 || extension.len() > 3 {
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
filename,
|
||||
extension,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eq_ignore_case(&self, other: &Self) -> bool {
|
||||
self.filename.eq_ignore_case(other.filename)
|
||||
&& self.extension.eq_ignore_case(other.extension)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ShortFilename<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "\"{}", self.filename)?;
|
||||
if !self.extension.is_empty() {
|
||||
write!(f, ".{}", self.extension)?;
|
||||
}
|
||||
write!(f, "\"")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{data::FatStr, directory::ShortFilename};
|
||||
|
||||
#[test]
|
||||
fn test_short_filename_from_str() {
|
||||
assert_eq!(
|
||||
ShortFilename::from_str("a1b2c3d4.txt"),
|
||||
Some(ShortFilename {
|
||||
filename: FatStr::new(b"a1b2c3d4"),
|
||||
extension: FatStr::new(b"txt")
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
ShortFilename::from_str("a1b2c3d4"),
|
||||
Some(ShortFilename {
|
||||
filename: FatStr::new(b"a1b2c3d4"),
|
||||
extension: FatStr::new(b"")
|
||||
})
|
||||
);
|
||||
assert_eq!(ShortFilename::from_str("a1b2c3d4e5.txt"), None);
|
||||
assert_eq!(ShortFilename::from_str("a1b2c3d4.long"), None);
|
||||
assert_eq!(ShortFilename::from_str("."), None);
|
||||
assert_eq!(ShortFilename::from_str(""), None);
|
||||
assert_eq!(ShortFilename::from_str(".ext"), None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_filename_ignore_case_eq() {
|
||||
assert!(ShortFilename::eq_ignore_case(
|
||||
&ShortFilename::from_str("abcdefgh.txt").unwrap(),
|
||||
&ShortFilename::from_str("AbCdEfGh.txT").unwrap()
|
||||
));
|
||||
}
|
||||
}
|
156
kernel/driver/fs/fat32/src/file.rs
Normal file
156
kernel/driver/fs/fat32/src/file.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use core::any::Any;
|
||||
|
||||
use alloc::{sync::Arc, vec, vec::Vec};
|
||||
use libk::{
|
||||
block,
|
||||
error::Error,
|
||||
task::sync::AsyncMutex,
|
||||
vfs::{CommonImpl, InstanceData, Metadata, NodeRef, RegularImpl},
|
||||
};
|
||||
use yggdrasil_abi::io::OpenOptions;
|
||||
|
||||
use crate::{data::ClusterNumber, Fat32Fs};
|
||||
|
||||
pub struct FileNode {
|
||||
pub(crate) fs: Arc<Fat32Fs>,
|
||||
pub(crate) cluster: ClusterNumber,
|
||||
pub(crate) size_bytes: u64,
|
||||
pub(crate) metadata: Metadata,
|
||||
// Will be used when metadata needs to be updated
|
||||
#[allow(unused)]
|
||||
pub(crate) parent: Option<ClusterNumber>,
|
||||
}
|
||||
|
||||
// TODO use a "sliding window" to minimize memory usage when working with large files?
|
||||
struct OpenedFile {
|
||||
cluster_chain: AsyncMutex<Vec<ClusterNumber>>,
|
||||
}
|
||||
|
||||
impl OpenedFile {
|
||||
fn new(first_cluster: ClusterNumber) -> Self {
|
||||
Self {
|
||||
cluster_chain: AsyncMutex::new(vec![first_cluster]),
|
||||
}
|
||||
}
|
||||
|
||||
async fn seek(&self, file: &FileNode, cluster_index: usize) -> Result<ClusterNumber, Error> {
|
||||
let mut chain = self.cluster_chain.lock().await;
|
||||
if cluster_index >= chain.len() {
|
||||
let last = *chain.last().unwrap();
|
||||
file.fs
|
||||
.iterate_clusters(last, cluster_index + 1 - chain.len(), |cluster| {
|
||||
chain.push(cluster);
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(chain.len(), cluster_index + 1);
|
||||
}
|
||||
|
||||
Ok(chain[cluster_index])
|
||||
}
|
||||
}
|
||||
|
||||
impl FileNode {
|
||||
async fn read_inner(
|
||||
&self,
|
||||
instance: &OpenedFile,
|
||||
mut pos: u64,
|
||||
buffer: &mut [u8],
|
||||
) -> Result<usize, Error> {
|
||||
if pos >= self.size_bytes {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let len = buffer.len().min((self.size_bytes - pos) as usize);
|
||||
let mut rem = len;
|
||||
let mut offset = 0;
|
||||
|
||||
let bps = self.fs.layout.bytes_per_sector as u64;
|
||||
let spc = self.fs.layout.sectors_per_cluster as u64;
|
||||
|
||||
while rem != 0 {
|
||||
let cluster_index = pos / (bps * spc);
|
||||
let sector_in_cluster = (pos / bps) % spc;
|
||||
let offset_in_sector = (pos % bps) as usize;
|
||||
let amount = rem.min(bps as usize - offset_in_sector);
|
||||
|
||||
let cluster = instance.seek(self, cluster_index as usize).await?;
|
||||
|
||||
self.fs
|
||||
.with_cluster_sector(cluster, sector_in_cluster, |data| {
|
||||
buffer[offset..offset + amount]
|
||||
.copy_from_slice(&data[offset_in_sector..offset_in_sector + amount]);
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
rem -= amount;
|
||||
offset += amount;
|
||||
pos += amount as u64;
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonImpl for FileNode {
|
||||
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
|
||||
Ok(self.metadata)
|
||||
}
|
||||
|
||||
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
||||
Ok(self.size_bytes)
|
||||
}
|
||||
|
||||
fn set_metadata(&self, _node: &NodeRef, _metadata: &Metadata) -> Result<(), Error> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RegularImpl for FileNode {
|
||||
fn open(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
opts: OpenOptions,
|
||||
) -> Result<(u64, Option<InstanceData>), Error> {
|
||||
if opts.contains_any(OpenOptions::TRUNCATE | OpenOptions::APPEND | OpenOptions::WRITE) {
|
||||
return Err(Error::ReadOnly);
|
||||
}
|
||||
let instance = Arc::new(OpenedFile::new(self.cluster));
|
||||
Ok((0, Some(instance)))
|
||||
}
|
||||
|
||||
fn close(&self, _node: &NodeRef, _instance: Option<&InstanceData>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
instance: Option<&InstanceData>,
|
||||
pos: u64,
|
||||
buf: &mut [u8],
|
||||
) -> Result<usize, Error> {
|
||||
let instance = instance
|
||||
.and_then(|p| p.downcast_ref::<OpenedFile>())
|
||||
.ok_or(Error::InvalidFile)?;
|
||||
block!(self.read_inner(instance, pos, buf).await)?
|
||||
}
|
||||
|
||||
fn write(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
_instance: Option<&InstanceData>,
|
||||
_pos: u64,
|
||||
_buf: &[u8],
|
||||
) -> Result<usize, Error> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
|
||||
fn truncate(&self, _node: &NodeRef, _new_size: u64) -> Result<(), Error> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
}
|
225
kernel/driver/fs/fat32/src/lib.rs
Normal file
225
kernel/driver/fs/fat32/src/lib.rs
Normal file
@ -0,0 +1,225 @@
|
||||
#![no_std]
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use data::{Bpb, ClusterNumber, Fat32Ebpb, Fat32FsInfo};
|
||||
use directory::{DirectoryNode, DIRENT_SIZE};
|
||||
use libk::{
|
||||
device::block::{cache::DeviceMapper, BlockDevice},
|
||||
error::Error,
|
||||
vfs::{Filesystem, FilesystemMountOption, Metadata, Node, NodeFlags, NodeRef},
|
||||
};
|
||||
use libk_util::get_le_u32;
|
||||
use yggdrasil_abi::io::{FileMode, GroupId, UserId};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod data;
|
||||
|
||||
mod directory;
|
||||
mod file;
|
||||
|
||||
pub struct FsLayout {
|
||||
// From BPB/EBPB
|
||||
bytes_per_sector: usize,
|
||||
total_sectors: u64,
|
||||
sectors_per_fat: usize,
|
||||
sectors_per_cluster: usize,
|
||||
root_directory_cluster: ClusterNumber,
|
||||
root_directory_entries: usize,
|
||||
first_fat_sector: u64,
|
||||
|
||||
// Computed
|
||||
first_data_sector: u64,
|
||||
}
|
||||
|
||||
pub struct Fat32Fs {
|
||||
mapper: DeviceMapper,
|
||||
pub layout: FsLayout,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Filesystem for Fat32Fs {
|
||||
fn display_name(&self) -> &'static str {
|
||||
"fat32"
|
||||
}
|
||||
}
|
||||
|
||||
impl Fat32Fs {
|
||||
pub async fn create<'a, I: IntoIterator<Item = FilesystemMountOption<'a>>>(
|
||||
device: Arc<dyn BlockDevice>,
|
||||
options: I,
|
||||
) -> Result<NodeRef, Error> {
|
||||
let mut cached = true;
|
||||
for option in options {
|
||||
match option {
|
||||
FilesystemMountOption::Sync => cached = false,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let fs = Arc::new(Self::create_fs(device, cached).await?);
|
||||
|
||||
// Setup root node
|
||||
let root_metadata = Metadata {
|
||||
uid: UserId::root(),
|
||||
gid: GroupId::root(),
|
||||
mode: FileMode::default_dir(),
|
||||
ctime: 0,
|
||||
mtime: 0,
|
||||
inode: Some(fs.layout.root_directory_cluster.0),
|
||||
block_size: fs.layout.bytes_per_sector as u64,
|
||||
block_count: 0,
|
||||
};
|
||||
let root_directory = DirectoryNode {
|
||||
fs: fs.clone(),
|
||||
cluster: fs.layout.root_directory_cluster,
|
||||
parent: None,
|
||||
size_bytes: (fs.layout.root_directory_entries * DIRENT_SIZE) as u64,
|
||||
metadata: root_metadata,
|
||||
};
|
||||
let root_node = Node::directory(root_directory, NodeFlags::empty(), None, Some(fs.clone()));
|
||||
|
||||
Ok(root_node)
|
||||
}
|
||||
|
||||
async fn create_fs(device: Arc<dyn BlockDevice>, cached: bool) -> Result<Fat32Fs, Error> {
|
||||
let mut boot_sector = [0; 512];
|
||||
let mut fsinfo = [0; 512];
|
||||
device.read_exact(0, &mut boot_sector).await?;
|
||||
|
||||
let bpb = Bpb::from_bytes(&boot_sector);
|
||||
let ebpb = Fat32Ebpb::from_bytes(&boot_sector);
|
||||
let fsinfo_sector = ebpb.fsinfo_sector();
|
||||
let bytes_per_sector = bpb.bytes_per_sector();
|
||||
let total_sectors = bpb.total_sectors();
|
||||
|
||||
if bpb.fat_size_16() != 0 {
|
||||
log::warn!("fat32: not a FAT32 filesystem");
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
if fsinfo_sector >= total_sectors {
|
||||
log::warn!("fat32: FSInfo sector is beyond filesystem end");
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
device
|
||||
.read_exact(bytes_per_sector as u64 * fsinfo_sector, &mut fsinfo)
|
||||
.await?;
|
||||
|
||||
let fsinfo = Fat32FsInfo::from_bytes(fsinfo);
|
||||
if !fsinfo.is_valid_fat32() {
|
||||
log::warn!("fat32: not a FAT32 filesystem");
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
let sectors_per_fat = ebpb.sectors_per_fat();
|
||||
let sectors_per_cluster = bpb.sectors_per_cluster();
|
||||
let root_directory_cluster = ebpb.root_directory_cluster();
|
||||
let root_directory_entries = bpb.root_directory_entries();
|
||||
|
||||
// + RootDirSectors, but RootDirSectors = 0 on FAT32
|
||||
let first_data_sector = bpb.reserved_sectors() + sectors_per_fat * bpb.fat_count();
|
||||
let first_fat_sector = bpb.reserved_sectors();
|
||||
|
||||
log::info!("fat32: mounted {:?}", ebpb.volume_label());
|
||||
log::info!(
|
||||
"fat32: sector {}B, cluster {}B",
|
||||
bytes_per_sector,
|
||||
sectors_per_cluster * bytes_per_sector
|
||||
);
|
||||
|
||||
let layout = FsLayout {
|
||||
bytes_per_sector,
|
||||
total_sectors,
|
||||
sectors_per_fat,
|
||||
sectors_per_cluster,
|
||||
root_directory_cluster,
|
||||
root_directory_entries,
|
||||
|
||||
first_fat_sector: first_fat_sector as u64,
|
||||
first_data_sector: first_data_sector as u64,
|
||||
};
|
||||
|
||||
let mapper = if cached {
|
||||
DeviceMapper::cached_with_capacity(
|
||||
device,
|
||||
// Most often 512
|
||||
layout.bytes_per_sector,
|
||||
// 512 * 16 * 4 = 32768
|
||||
(layout.bytes_per_sector * layout.sectors_per_cluster) * 4,
|
||||
128,
|
||||
64,
|
||||
"fat32",
|
||||
)?
|
||||
} else {
|
||||
DeviceMapper::uncached(device, layout.bytes_per_sector, "fat32")?
|
||||
};
|
||||
|
||||
Ok(Self { layout, mapper })
|
||||
}
|
||||
|
||||
async fn with_cluster_sector<T, F: FnOnce(&[u8]) -> Result<T, Error>>(
|
||||
&self,
|
||||
cluster: ClusterNumber,
|
||||
sector_in_cluster: u64,
|
||||
mapper: F,
|
||||
) -> Result<T, Error> {
|
||||
let sector = cluster.first_sector(&self.layout) + sector_in_cluster;
|
||||
if sector >= self.layout.total_sectors {
|
||||
log::warn!("fat32: sector {sector} beyond filesystem end");
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
self.mapper
|
||||
.try_with(sector * self.layout.bytes_per_sector as u64, mapper)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn with_fat_sector<T, F: FnOnce(&[u8]) -> Result<T, Error>>(
|
||||
&self,
|
||||
fat_sector: u64,
|
||||
mapper: F,
|
||||
) -> Result<T, Error> {
|
||||
if fat_sector >= self.layout.sectors_per_fat as u64 {
|
||||
log::warn!("fat32: FAT sector {fat_sector} outside of FAT size");
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
let sector = self.layout.first_fat_sector + fat_sector;
|
||||
self.mapper
|
||||
.try_with(sector * self.layout.bytes_per_sector as u64, mapper)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn iterate_clusters<F: FnMut(ClusterNumber)>(
|
||||
&self,
|
||||
start: ClusterNumber,
|
||||
count: usize,
|
||||
mut mapper: F,
|
||||
) -> Result<(), Error> {
|
||||
let mut current = start;
|
||||
|
||||
for _ in 0..count {
|
||||
let offset_in_fat = current.0 as usize * size_of::<u32>();
|
||||
let sector_in_fat = (offset_in_fat / self.layout.bytes_per_sector) as u64;
|
||||
let offset_in_sector = offset_in_fat % self.layout.bytes_per_sector;
|
||||
|
||||
let cluster = self
|
||||
.with_fat_sector(sector_in_fat, |buffer| {
|
||||
let number = get_le_u32(&buffer[offset_in_sector..]) & 0x0FFFFFFF;
|
||||
Ok(number)
|
||||
})
|
||||
.await?;
|
||||
|
||||
if cluster >= 0x0FFFFFF7 {
|
||||
return Err(Error::InvalidFile);
|
||||
}
|
||||
|
||||
let cluster = ClusterNumber(cluster);
|
||||
mapper(cluster);
|
||||
current = cluster;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@
|
||||
maybe_uninit_slice,
|
||||
allocator_api,
|
||||
let_chains,
|
||||
const_trait_impl
|
||||
const_trait_impl,
|
||||
str_from_utf16_endian
|
||||
)]
|
||||
#![allow(clippy::new_without_default)]
|
||||
|
||||
@ -24,6 +25,7 @@ pub mod io;
|
||||
pub mod lru_hash_table;
|
||||
pub mod queue;
|
||||
pub mod ring;
|
||||
pub mod string;
|
||||
pub mod sync;
|
||||
pub mod waker;
|
||||
|
||||
@ -152,6 +154,14 @@ impl<T, const N: usize> DerefMut for StaticVector<T, N> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_le_u16(bytes: &[u8]) -> u16 {
|
||||
u16::from_le_bytes([bytes[0], bytes[1]])
|
||||
}
|
||||
|
||||
pub fn get_le_u32(bytes: &[u8]) -> u32 {
|
||||
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::RangeExt;
|
||||
|
194
kernel/libk/libk-util/src/string.rs
Normal file
194
kernel/libk/libk-util/src/string.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use core::{fmt, slice::ChunksExact};
|
||||
|
||||
use crate::get_le_u16;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Utf16Error {
|
||||
InvalidLength,
|
||||
TrailingSurrogates,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct Utf16LeStr {
|
||||
raw: [u8],
|
||||
}
|
||||
|
||||
pub struct Utf16LeIter<'a> {
|
||||
chars: ChunksExact<'a, u8>,
|
||||
}
|
||||
|
||||
impl Utf16LeStr {
|
||||
pub fn from_utf16le(raw: &[u8]) -> Result<&Self, Utf16Error> {
|
||||
Self::validate(raw)?;
|
||||
Ok(unsafe { Self::from_utf16le_unchecked(raw) })
|
||||
}
|
||||
|
||||
pub unsafe fn from_utf16le_unchecked(raw: &[u8]) -> &Self {
|
||||
core::mem::transmute(raw)
|
||||
}
|
||||
|
||||
pub fn chars(&self) -> Utf16LeIter {
|
||||
Utf16LeIter {
|
||||
chars: self.raw.chunks_exact(size_of::<u16>()),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(raw: &[u8]) -> Result<(), Utf16Error> {
|
||||
if raw.len() % 2 != 0 {
|
||||
return Err(Utf16Error::InvalidLength);
|
||||
}
|
||||
|
||||
let mut iter = raw.chunks_exact(size_of::<u16>());
|
||||
while let Some(chunk) = iter.next() {
|
||||
let word = get_le_u16(chunk);
|
||||
|
||||
if is_low_surrogate(word) {
|
||||
return Err(Utf16Error::TrailingSurrogates);
|
||||
}
|
||||
|
||||
if is_high_surrogate(word) {
|
||||
let Some(chunk) = iter.next() else {
|
||||
return Err(Utf16Error::TrailingSurrogates);
|
||||
};
|
||||
let word = get_le_u16(chunk);
|
||||
|
||||
if !is_low_surrogate(word) {
|
||||
return Err(Utf16Error::TrailingSurrogates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Utf16LeIter<'_> {
|
||||
type Item = char;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let chunk = self.chars.next()?;
|
||||
let word = get_le_u16(chunk);
|
||||
|
||||
if !is_high_surrogate(word) {
|
||||
Some(unsafe { core::char::from_u32_unchecked(word as u32) })
|
||||
} else {
|
||||
let chunk = unsafe { self.chars.next().unwrap_unchecked() };
|
||||
let next = get_le_u16(chunk);
|
||||
let ch = (((word as u32 - 0xD800) << 10) | (next as u32 - 0xDC00)) + 0x10000;
|
||||
Some(unsafe { core::char::from_u32_unchecked(ch) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Utf16LeStr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "\"")?;
|
||||
for ch in self.chars() {
|
||||
// TODO escaping
|
||||
write!(f, "{ch}")?;
|
||||
}
|
||||
write!(f, "\"")
|
||||
}
|
||||
}
|
||||
|
||||
fn is_high_surrogate(word: u16) -> bool {
|
||||
word & 0xFC00 == 0xD800
|
||||
}
|
||||
|
||||
fn is_low_surrogate(word: u16) -> bool {
|
||||
word & 0xFC00 == 0xDC00
|
||||
}
|
||||
|
||||
pub fn char_equal_ignore_case(c0: char, c1: char) -> bool {
|
||||
let mut i0 = c0.to_lowercase();
|
||||
let mut i1 = c1.to_lowercase();
|
||||
|
||||
loop {
|
||||
match (i0.next(), i1.next()) {
|
||||
(Some(c0), Some(c1)) if c0 == c1 => (),
|
||||
(None, None) => return true,
|
||||
(_, _) => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chars_equal_ignore_case<I0: Iterator<Item = char>, I1: Iterator<Item = char>>(
|
||||
mut i0: I0,
|
||||
mut i1: I1,
|
||||
) -> bool {
|
||||
loop {
|
||||
match (i0.next(), i1.next()) {
|
||||
(Some(c0), Some(c1)) if char_equal_ignore_case(c0, c1) => (),
|
||||
(None, None) => return true,
|
||||
(_, _) => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::string::chars_equal_ignore_case;
|
||||
|
||||
use super::Utf16LeStr;
|
||||
|
||||
fn encode_utf16le<S: AsRef<str> + ?Sized>(s: &S) -> Vec<u8> {
|
||||
s.as_ref()
|
||||
.encode_utf16()
|
||||
.flat_map(|word| word.to_le_bytes())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn chars_equal<I0: Iterator<Item = char>, I1: Iterator<Item = char>>(
|
||||
mut i0: I0,
|
||||
mut i1: I1,
|
||||
) -> bool {
|
||||
loop {
|
||||
match (i0.next(), i1.next()) {
|
||||
(Some(c0), Some(c1)) if c0 == c1 => (),
|
||||
(None, None) => return true,
|
||||
(_, _) => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chars_equal_ignore_case() {
|
||||
assert!(!chars_equal_ignore_case("abc".chars(), "def".chars()));
|
||||
assert!(!chars_equal_ignore_case("abc".chars(), "ab".chars()));
|
||||
assert!(!chars_equal_ignore_case("abc".chars(), "abcd".chars()));
|
||||
assert!(chars_equal_ignore_case("abc".chars(), "abc".chars()));
|
||||
assert!(chars_equal_ignore_case("aBc".chars(), "ABc".chars()));
|
||||
assert!(chars_equal_ignore_case(
|
||||
"Україна".chars(),
|
||||
"УКРАЇНА".chars()
|
||||
));
|
||||
assert!(chars_equal_ignore_case("日本".chars(), "日本".chars()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_utf16le_str() {
|
||||
let input = "abcdef";
|
||||
let bytes = encode_utf16le(input);
|
||||
let utf16 = Utf16LeStr::from_utf16le(&bytes).unwrap();
|
||||
assert!(chars_equal(utf16.chars(), input.chars()));
|
||||
|
||||
let input = "юнікод";
|
||||
let bytes = encode_utf16le(input);
|
||||
let utf16 = Utf16LeStr::from_utf16le(&bytes).unwrap();
|
||||
assert!(chars_equal(utf16.chars(), input.chars()));
|
||||
|
||||
let input = "世界";
|
||||
let bytes = encode_utf16le(input);
|
||||
let utf16 = Utf16LeStr::from_utf16le(&bytes).unwrap();
|
||||
assert!(chars_equal(utf16.chars(), input.chars()));
|
||||
|
||||
let input = "\u{01F995}";
|
||||
let bytes = encode_utf16le(input);
|
||||
let utf16 = Utf16LeStr::from_utf16le(&bytes).unwrap();
|
||||
let codepoint = utf16.chars().next().unwrap();
|
||||
assert_eq!(codepoint, '\u{01F995}');
|
||||
}
|
||||
}
|
@ -460,6 +460,47 @@ pub fn init() {
|
||||
init_logger();
|
||||
}
|
||||
|
||||
pub fn hex_dump(level: log::Level, bytes: &[u8], start_address: u64, with_chars: bool) {
|
||||
if bytes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let line_width = 16;
|
||||
let address_width = (start_address + bytes.len() as u64).ilog10() as usize + 1;
|
||||
|
||||
for i in (0..bytes.len()).step_by(line_width) {
|
||||
log::log!(target: ":raw", level, "{:0width$X}: ", start_address + i as u64, width = address_width);
|
||||
|
||||
for j in 0..line_width {
|
||||
if i + j < bytes.len() {
|
||||
log::log!(target: ":raw", level, "{:02X}", bytes[i + j]);
|
||||
} else {
|
||||
log::log!(target: ":raw", level, " ");
|
||||
}
|
||||
|
||||
if j % 2 != 0 {
|
||||
log::log!(target: ":raw", level, " ");
|
||||
}
|
||||
}
|
||||
|
||||
if with_chars {
|
||||
for j in 0..line_width {
|
||||
if i + j >= bytes.len() {
|
||||
break;
|
||||
}
|
||||
let ch = if bytes[i + j].is_ascii_graphic() {
|
||||
bytes[i + j]
|
||||
} else {
|
||||
b'.'
|
||||
};
|
||||
log::log!(target: ":raw", level, "{}", ch as char);
|
||||
}
|
||||
}
|
||||
|
||||
log::log!(target: ":raw", level, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LogLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let level = match self {
|
||||
|
@ -18,6 +18,7 @@ use libk_util::OneTimeInit;
|
||||
use memfs::block::{self, BlockAllocator};
|
||||
// use memfs::block::{self, BlockAllocator};
|
||||
use static_assertions::const_assert_eq;
|
||||
use ygg_driver_fat32::Fat32Fs;
|
||||
use yggdrasil_abi::{error::Error, io::MountOptions};
|
||||
|
||||
pub use pseudo::add_pseudo_devices;
|
||||
@ -89,6 +90,11 @@ fn create_filesystem<'a, I: IntoIterator<Item = FilesystemMountOption<'a>>>(
|
||||
let device = source.as_block_device()?;
|
||||
block!(Ext2Fs::create(device.clone(), options).await)??
|
||||
}
|
||||
"fat32" if let Some(source) = source => {
|
||||
let source = source?;
|
||||
let device = source.as_block_device()?;
|
||||
block!(Fat32Fs::create(device.clone(), options).await)??
|
||||
}
|
||||
_ => return Err(Error::InvalidArgument),
|
||||
};
|
||||
|
||||
|
@ -19,6 +19,32 @@ impl<const N: usize> FixedString<N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies character codepoints, encoding them in UTF-8, into this [FixedString]. Returns
|
||||
/// false if the result cannot fit into the string.
|
||||
pub fn append_from_chars<I: Iterator<Item = char>>(&mut self, it: I) -> Result<usize, usize> {
|
||||
let start = self.len;
|
||||
for ch in it {
|
||||
let ch_len = ch.len_utf8();
|
||||
if self.len + ch_len > N {
|
||||
return Err(self.len - start);
|
||||
}
|
||||
ch.encode_utf8(&mut self.data[self.len..]);
|
||||
self.len += ch_len;
|
||||
}
|
||||
Ok(self.len - start)
|
||||
}
|
||||
|
||||
/// Appends bytes to the string. Returns false and does not change the string if there is not
|
||||
/// enough space to fit the whole `bytes` slice there.
|
||||
pub fn append_from_bytes(&mut self, bytes: &[u8]) -> bool {
|
||||
if self.len + bytes.len() > N {
|
||||
return false;
|
||||
}
|
||||
self.data[self.len..self.len + bytes.len()].copy_from_slice(bytes);
|
||||
self.len += bytes.len();
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns the length of the string
|
||||
pub const fn len(&self) -> usize {
|
||||
self.len
|
||||
@ -93,3 +119,30 @@ pub fn from_bytes<T>(bytes: &[u8]) -> &T {
|
||||
assert_eq!(bytes.len(), core::mem::size_of::<T>());
|
||||
unsafe { &*(&bytes[0] as *const u8 as *const T) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FixedString;
|
||||
|
||||
#[test]
|
||||
fn test_fixed_string_append() {
|
||||
let mut fixed = FixedString::<16>::empty();
|
||||
assert!(fixed.append_from_bytes(b"test"));
|
||||
assert!(fixed.append_from_bytes(b"2test"));
|
||||
assert!(fixed.append_from_bytes(b"3test"));
|
||||
assert_eq!(fixed, "test2test3test");
|
||||
assert!(!fixed.append_from_bytes(b"4test"));
|
||||
assert_eq!(fixed, "test2test3test");
|
||||
|
||||
let mut fixed = FixedString::<256>::empty();
|
||||
assert_eq!(fixed.append_from_chars("test".chars()), Ok(4));
|
||||
assert_eq!(fixed, "test");
|
||||
assert_eq!(
|
||||
fixed.append_from_chars("very0very1very2very3very4longfilename.txt".chars()),
|
||||
Ok(41)
|
||||
);
|
||||
assert_eq!(fixed, "testvery0very1very2very3very4longfilename.txt");
|
||||
assert_eq!(fixed.append_from_chars("юнікод".chars()), Ok(12));
|
||||
assert_eq!(fixed, "testvery0very1very2very3very4longfilename.txtюнікод");
|
||||
}
|
||||
}
|
||||
|
@ -158,6 +158,7 @@ pub fn check_all(env: BuildEnv, action: CheckAction) -> Result<(), Error> {
|
||||
pub fn test_all(env: BuildEnv) -> Result<(), Error> {
|
||||
for path in [
|
||||
"kernel/driver/fs/memfs",
|
||||
"kernel/driver/fs/fat32",
|
||||
"lib/abi",
|
||||
"kernel/libk",
|
||||
"kernel/libk/libk-util",
|
||||
|
Loading…
x
Reference in New Issue
Block a user