use alloc::{format, string::String, sync::Arc, vec, vec::Vec};
use axerrno::{AxResult, ax_err};
use axfs_vfs::VfsOps;
use log::{debug, info, warn};
use crate::dev::Disk;
#[derive(Debug, Clone)]
pub struct PartitionInfo {
pub index: u32,
pub name: String,
#[allow(dead_code)]
pub partition_type_guid: [u8; 16],
pub unique_partition_guid: [u8; 16],
pub filesystem_uuid: Option<String>,
pub starting_lba: u64,
pub ending_lba: u64,
pub size_bytes: u64,
pub filesystem_type: Option<FilesystemType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilesystemType {
Fat,
Ext4,
Unknown,
}
#[repr(C, packed)]
struct GptHeader {
signature: [u8; 8], revision: [u8; 4],
header_size: [u8; 4],
header_crc32: [u8; 4],
reserved: [u8; 4],
current_lba: [u8; 8],
backup_lba: [u8; 8],
first_usable_lba: [u8; 8],
last_usable_lba: [u8; 8],
disk_guid: [u8; 16],
partition_entry_lba: [u8; 8],
number_of_partition_entries: [u8; 4],
size_of_partition_entry: [u8; 4],
partition_entry_array_crc32: [u8; 4],
}
#[repr(C, packed)]
struct GptPartitionEntry {
partition_type_guid: [u8; 16],
unique_partition_guid: [u8; 16],
starting_lba: [u8; 8],
ending_lba: [u8; 8],
attributes: [u8; 8],
partition_name: [u16; 36], }
pub fn scan_gpt_partitions(disk: &mut Disk) -> AxResult<Vec<PartitionInfo>> {
info!("Scanning for GPT partitions...");
let disk_size = disk.size();
if disk_size == 0 {
return Ok(Vec::new());
}
match parse_gpt_partitions(disk) {
Ok(partitions) if !partitions.is_empty() => {
info!("Found {} GPT partitions", partitions.len());
return Ok(partitions);
}
Ok(_) => {
info!("No GPT partitions found, trying MBR...");
}
Err(e) => {
warn!("Failed to parse GPT: {:?}", e);
info!("Trying MBR...");
}
}
warn!("No partition table found, treating whole disk as single partition");
let filesystem_type = detect_filesystem_type(disk, 0);
let partition = PartitionInfo {
index: 0,
name: String::from("disk"),
partition_type_guid: [0; 16],
unique_partition_guid: [0; 16],
filesystem_uuid: None,
starting_lba: 0,
ending_lba: disk_size / 512,
size_bytes: disk_size,
filesystem_type,
};
Ok(vec![partition])
}
fn parse_gpt_partitions(disk: &mut Disk) -> AxResult<Vec<PartitionInfo>> {
let mut partitions = Vec::new();
let mut header_data = [0u8; 512];
disk.set_position(512); if read_exact(disk, &mut header_data).is_err() {
return ax_err!(InvalidData, "Failed to read GPT header");
}
if &header_data[0..8] != b"EFI PART" {
return ax_err!(InvalidData, "Invalid GPT signature");
}
let header = GptHeader {
signature: header_data[0..8].try_into().unwrap(),
revision: header_data[8..12].try_into().unwrap(),
header_size: header_data[12..16].try_into().unwrap(),
header_crc32: header_data[16..20].try_into().unwrap(),
reserved: header_data[20..24].try_into().unwrap(),
current_lba: header_data[24..32].try_into().unwrap(),
backup_lba: header_data[32..40].try_into().unwrap(),
first_usable_lba: header_data[40..48].try_into().unwrap(),
last_usable_lba: header_data[48..56].try_into().unwrap(),
disk_guid: header_data[56..72].try_into().unwrap(),
partition_entry_lba: header_data[72..80].try_into().unwrap(),
number_of_partition_entries: header_data[80..84].try_into().unwrap(),
size_of_partition_entry: header_data[84..88].try_into().unwrap(),
partition_entry_array_crc32: header_data[88..92].try_into().unwrap(),
};
let partition_entry_lba = u64::from_le_bytes(header.partition_entry_lba);
let number_of_partition_entries = u32::from_le_bytes(header.number_of_partition_entries);
let size_of_partition_entry = u32::from_le_bytes(header.size_of_partition_entry);
info!(
"GPT Header: {} entries at LBA {}",
number_of_partition_entries, partition_entry_lba
);
let partition_entry_offset = partition_entry_lba * 512;
disk.set_position(partition_entry_offset);
debug!("Partition entry size: {} bytes", size_of_partition_entry);
debug!("Partition entry offset: {} bytes", partition_entry_offset);
for i in 0..number_of_partition_entries {
let current_entry_offset =
partition_entry_offset + (i as u64 * size_of_partition_entry as u64);
disk.set_position(current_entry_offset);
let mut entry_data = vec![0u8; size_of_partition_entry as usize];
if read_exact(disk, &mut entry_data).is_err() {
warn!("Failed to read partition entry {}", i);
continue;
}
let entry = if size_of_partition_entry >= 128 {
let partition_type_guid: [u8; 16] = entry_data[0..16].try_into().unwrap();
let unique_partition_guid: [u8; 16] = entry_data[16..32].try_into().unwrap();
let starting_lba: [u8; 8] = entry_data[32..40].try_into().unwrap();
let ending_lba: [u8; 8] = entry_data[40..48].try_into().unwrap();
let attributes: [u8; 8] = entry_data[48..56].try_into().unwrap();
let mut partition_name = [0u16; 36];
for j in 0..36 {
let offset = 56 + j * 2;
if offset + 1 < entry_data.len() {
partition_name[j] =
u16::from_le_bytes([entry_data[offset], entry_data[offset + 1]]);
}
}
GptPartitionEntry {
partition_type_guid,
unique_partition_guid,
starting_lba,
ending_lba,
attributes,
partition_name,
}
} else {
continue;
};
if entry.partition_type_guid.iter().all(|&b| b == 0) {
continue;
}
let starting_lba = u64::from_le_bytes(entry.starting_lba);
let ending_lba = u64::from_le_bytes(entry.ending_lba);
let size_bytes = (ending_lba - starting_lba + 1) * 512;
let name_str = {
let mut name_utf16 = [0u16; 36];
for j in 0..36 {
name_utf16[j] = entry.partition_name[j];
}
let mut name_len = 36;
for j in 0..36 {
if name_utf16[j] == 0 {
name_len = j;
break;
}
}
let name_slice = &name_utf16[..name_len];
let name_str = String::from_utf16_lossy(name_slice);
debug!(
"Partition {}: UTF-16LE name (len={}): {:?}, UTF-8 name: '{}'",
i, name_len, name_slice, name_str
);
name_str
};
if name_str.is_empty() {
continue;
}
let (filesystem_type, filesystem_uuid) = {
let fs_type = detect_filesystem_type(disk, starting_lba);
let uuid = if let Some(ref fs) = fs_type {
read_filesystem_uuid_simple(disk, starting_lba, fs)
} else {
None
};
(fs_type, uuid)
};
let partition = PartitionInfo {
index: i as u32,
name: name_str,
partition_type_guid: entry.partition_type_guid,
unique_partition_guid: entry.unique_partition_guid,
filesystem_uuid,
starting_lba,
ending_lba,
size_bytes,
filesystem_type,
};
info!(
"Found GPT partition {}: '{}' ({} bytes) with filesystem: {:?}",
partition.index, partition.name, partition.size_bytes, partition.filesystem_type,
);
partitions.push(partition);
}
Ok(partitions)
}
fn detect_filesystem_type(disk: &mut Disk, start_lba: u64) -> Option<FilesystemType> {
let mut boot_sector = [0u8; 512];
let original_position = disk.position();
disk.set_position(start_lba * 512);
if let Err(_) = read_exact(disk, &mut boot_sector) {
warn!("Failed to read boot sector at LBA {}", start_lba);
disk.set_position(original_position);
return None;
}
disk.set_position(original_position);
if is_fat_filesystem(&boot_sector) {
debug!("Detected FAT filesystem at LBA {}", start_lba);
return Some(FilesystemType::Fat);
}
if is_ext4_filesystem(disk, start_lba) {
debug!("Detected ext4 filesystem at LBA {}", start_lba);
return Some(FilesystemType::Ext4);
}
debug!("Unknown filesystem type at LBA {}", start_lba);
None
}
fn read_exact(disk: &mut Disk, mut buf: &mut [u8]) -> Result<(), ()> {
while !buf.is_empty() {
match disk.read_one(buf) {
Ok(0) => break,
Ok(n) => buf = &mut buf[n..],
Err(_) => return Err(()),
}
}
Ok(())
}
fn is_fat_filesystem(boot_sector: &[u8; 512]) -> bool {
if boot_sector.len() >= 0x36 + 3 {
let fat_sig = &boot_sector[0x36..0x36 + 3];
if fat_sig == b"FAT" {
return true;
}
}
if boot_sector.len() >= 0x52 + 5 {
let fat32_sig = &boot_sector[0x52..0x52 + 5];
if fat32_sig == b"FAT32" {
return true;
}
}
false
}
fn is_ext4_filesystem(disk: &mut Disk, start_lba: u64) -> bool {
let superblock_offset = start_lba * 512 + 1024;
let mut superblock = [0u8; 2048];
let pos = disk.position();
disk.set_position(superblock_offset);
let result = if let Err(_) = read_exact(disk, &mut superblock) {
warn!(
"Failed to read ext4 superblock at offset {}",
superblock_offset
);
false
} else {
if superblock.len() >= 58 {
let magic = u16::from_le_bytes([superblock[56], superblock[57]]);
magic == 0xEF53
} else {
false
}
};
disk.set_position(pos);
result
}
pub fn create_filesystem_for_partition(
disk: Disk,
partition: &PartitionInfo,
) -> AxResult<Arc<dyn VfsOps>> {
match partition.filesystem_type {
Some(FilesystemType::Fat) => {
info!("Creating FAT filesystem for partition '{}'", partition.name);
let partition_wrapper =
crate::dev::Partition::new(disk, partition.starting_lba, partition.ending_lba);
let fs = crate::fs::fatfs::FatFileSystem::from_partition(partition_wrapper);
Ok(Arc::new(fs))
}
Some(FilesystemType::Ext4) => {
info!(
"Creating ext4 filesystem for partition '{}'",
partition.name
);
let partition_wrapper =
crate::dev::Partition::new(disk, partition.starting_lba, partition.ending_lba);
let fs = crate::fs::ext4fs::Ext4FileSystem::from_partition(partition_wrapper);
Ok(Arc::new(fs))
}
Some(FilesystemType::Unknown) | None => {
warn!("Unknown filesystem type for partition '{}'", partition.name);
ax_err!(Unsupported, "Unknown filesystem type")
}
}
}
fn read_filesystem_uuid_simple(
disk: &mut Disk,
starting_lba: u64,
filesystem_type: &FilesystemType,
) -> Option<String> {
match filesystem_type {
FilesystemType::Ext4 => read_ext4_uuid(disk, starting_lba),
FilesystemType::Fat => read_fat32_uuid(disk, starting_lba),
_ => None,
}
}
fn read_ext4_uuid(disk: &mut Disk, starting_lba: u64) -> Option<String> {
let superblock_offset = starting_lba * 512 + 1024;
disk.set_position(superblock_offset);
let mut superblock_data = vec![0u8; 1024];
let mut total_read = 0;
while total_read < 1024 {
match disk.read_one(&mut superblock_data[total_read..]) {
Ok(0) => break, Ok(n) => total_read += n,
Err(_) => return None,
}
}
if superblock_data.len() >= 120 {
let uuid_bytes = &superblock_data[104..120];
let uuid_str = format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
uuid_bytes[0],
uuid_bytes[1],
uuid_bytes[2],
uuid_bytes[3], uuid_bytes[4],
uuid_bytes[5], uuid_bytes[6],
uuid_bytes[7], uuid_bytes[8],
uuid_bytes[9], uuid_bytes[10],
uuid_bytes[11],
uuid_bytes[12],
uuid_bytes[13],
uuid_bytes[14],
uuid_bytes[15] );
Some(uuid_str)
} else {
None
}
}
fn read_fat32_uuid(disk: &mut Disk, starting_lba: u64) -> Option<String> {
let boot_sector_offset = starting_lba * 512;
disk.set_position(boot_sector_offset);
let mut boot_sector = vec![0u8; 512];
let mut total_read = 0;
while total_read < 512 {
match disk.read_one(&mut boot_sector[total_read..]) {
Ok(0) => break, Ok(n) => total_read += n,
Err(_) => return None,
}
}
if boot_sector.len() >= 71 {
let volume_id_bytes = &boot_sector[67..71];
let volume_id_str = format!(
"{:02x}{:02x}{:02x}{:02x}",
volume_id_bytes[3],
volume_id_bytes[2],
volume_id_bytes[1],
volume_id_bytes[0] );
Some(volume_id_str)
} else {
None
}
}