mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-03-16 07:56:38 +08:00
- 后端:切换事务+transition_id,/stream/mode 返回 switching/transition_id 与实际 codec - 事件:新增 mode_switching/mode_ready,config/webrtc_ready/mode_changed 关联事务 - 编码/格式:扩展 NV21/NV16/NV24/RGB/BGR 输入与转换链路,RKMPP direct input 优化 - 前端:useVideoSession 统一切换,失败回退真实切回 MJPEG,菜单格式同步修复 - 清理:useVideoStream 降级为 MJPEG-only
432 lines
15 KiB
Rust
432 lines
15 KiB
Rust
//! exFAT filesystem formatting
|
|
|
|
use crate::error::Result;
|
|
use crate::exfat::unicode;
|
|
use std::io::{Seek, SeekFrom, Write};
|
|
|
|
/// exFAT cluster size based on volume size
|
|
///
|
|
/// exFAT specification recommendations:
|
|
/// - < 256MB: 4KB clusters
|
|
/// - 256MB - 32GB: 32KB clusters
|
|
/// - 32GB - 256GB: 128KB clusters
|
|
/// - > 256GB: 256KB clusters (but we cap at 128KB for simplicity)
|
|
///
|
|
/// Note: Smaller clusters reduce waste but increase FAT table size and metadata overhead.
|
|
/// Larger clusters improve performance but waste space on small files.
|
|
fn get_cluster_size(total_sectors: u64) -> u32 {
|
|
let volume_size = total_sectors * 512; // Convert to bytes
|
|
|
|
match volume_size {
|
|
// < 256MB: Use 4KB clusters (good for many small files)
|
|
n if n < 256 * 1024 * 1024 => 4096,
|
|
// 256MB - 8GB: Use 32KB clusters (balanced)
|
|
n if n < 8 * 1024 * 1024 * 1024 => 32768,
|
|
// 8GB - 256GB: Use 128KB clusters (optimal for large ISOs)
|
|
_ => 128 * 1024,
|
|
}
|
|
}
|
|
|
|
/// Calculate sectors per cluster shift
|
|
///
|
|
/// Returns the power of 2 for sectors per cluster (512-byte sectors).
|
|
/// For example: 32KB cluster = 64 sectors = 2^6, so shift = 6
|
|
fn sectors_per_cluster_shift(cluster_size: u32) -> u8 {
|
|
match cluster_size {
|
|
4096 => 3, // 8 sectors (4KB)
|
|
8192 => 4, // 16 sectors (8KB)
|
|
16384 => 5, // 32 sectors (16KB)
|
|
32768 => 6, // 64 sectors (32KB)
|
|
65536 => 7, // 128 sectors (64KB)
|
|
131072 => 8, // 256 sectors (128KB)
|
|
262144 => 9, // 512 sectors (256KB)
|
|
_ => {
|
|
// Fallback: calculate dynamically
|
|
let sectors = cluster_size / 512;
|
|
(sectors.trailing_zeros() as u8).max(3).min(9)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// exFAT Boot Sector (512 bytes)
|
|
#[repr(C, packed)]
|
|
struct ExfatBootSector {
|
|
jump_boot: [u8; 3],
|
|
fs_name: [u8; 8],
|
|
must_be_zero: [u8; 53],
|
|
partition_offset: u64,
|
|
volume_length: u64,
|
|
fat_offset: u32,
|
|
fat_length: u32,
|
|
cluster_heap_offset: u32,
|
|
cluster_count: u32,
|
|
first_cluster_of_root: u32,
|
|
volume_serial_number: u32,
|
|
fs_revision: u16,
|
|
volume_flags: u16,
|
|
bytes_per_sector_shift: u8,
|
|
sectors_per_cluster_shift: u8,
|
|
number_of_fats: u8,
|
|
drive_select: u8,
|
|
percent_in_use: u8,
|
|
reserved: [u8; 7],
|
|
boot_code: [u8; 390],
|
|
boot_signature: u16,
|
|
}
|
|
|
|
impl ExfatBootSector {
|
|
fn new(volume_length: u64, cluster_size: u32, volume_serial: u32) -> Self {
|
|
let sector_size: u32 = 512;
|
|
let sectors_per_cluster = cluster_size / sector_size;
|
|
let spc_shift = sectors_per_cluster_shift(cluster_size);
|
|
|
|
// Calculate FAT offset (after boot region, typically sector 24)
|
|
let fat_offset: u32 = 24;
|
|
|
|
// Calculate cluster count and FAT length
|
|
// Cluster heap starts after FAT region
|
|
let usable_sectors = volume_length as u32 - fat_offset;
|
|
let cluster_count = (usable_sectors - 32) / sectors_per_cluster; // rough estimate
|
|
let fat_entries = cluster_count + 2; // cluster 0 and 1 are reserved
|
|
let fat_length = ((fat_entries * 4 + sector_size - 1) / sector_size).max(1);
|
|
|
|
// Cluster heap offset
|
|
let cluster_heap_offset = fat_offset + fat_length;
|
|
|
|
// Recalculate cluster count
|
|
let heap_sectors = volume_length as u32 - cluster_heap_offset;
|
|
let cluster_count = heap_sectors / sectors_per_cluster;
|
|
|
|
// Calculate root directory cluster based on upcase table size
|
|
// Cluster 2: Bitmap (1 cluster)
|
|
// Cluster 3...: Upcase table (128KB, may span multiple clusters)
|
|
// Next available: Root directory
|
|
const UPCASE_TABLE_SIZE: u64 = 128 * 1024;
|
|
let upcase_clusters =
|
|
((UPCASE_TABLE_SIZE + cluster_size as u64 - 1) / cluster_size as u64) as u32;
|
|
let first_cluster_of_root = 3 + upcase_clusters;
|
|
|
|
Self {
|
|
jump_boot: [0xEB, 0x76, 0x90],
|
|
fs_name: *b"EXFAT ",
|
|
must_be_zero: [0; 53],
|
|
partition_offset: 0,
|
|
volume_length,
|
|
fat_offset,
|
|
fat_length,
|
|
cluster_heap_offset,
|
|
cluster_count,
|
|
first_cluster_of_root,
|
|
volume_serial_number: volume_serial,
|
|
fs_revision: 0x0100,
|
|
volume_flags: 0,
|
|
bytes_per_sector_shift: 9, // 512 bytes
|
|
sectors_per_cluster_shift: spc_shift,
|
|
number_of_fats: 1,
|
|
drive_select: 0x80,
|
|
percent_in_use: 0xFF,
|
|
reserved: [0; 7],
|
|
boot_code: [0; 390],
|
|
boot_signature: 0xAA55,
|
|
}
|
|
}
|
|
|
|
fn to_bytes(&self) -> [u8; 512] {
|
|
let mut bytes = [0u8; 512];
|
|
|
|
bytes[0..3].copy_from_slice(&self.jump_boot);
|
|
bytes[3..11].copy_from_slice(&self.fs_name);
|
|
// bytes[11..64] already zero (must_be_zero)
|
|
bytes[64..72].copy_from_slice(&self.partition_offset.to_le_bytes());
|
|
bytes[72..80].copy_from_slice(&self.volume_length.to_le_bytes());
|
|
bytes[80..84].copy_from_slice(&self.fat_offset.to_le_bytes());
|
|
bytes[84..88].copy_from_slice(&self.fat_length.to_le_bytes());
|
|
bytes[88..92].copy_from_slice(&self.cluster_heap_offset.to_le_bytes());
|
|
bytes[92..96].copy_from_slice(&self.cluster_count.to_le_bytes());
|
|
bytes[96..100].copy_from_slice(&self.first_cluster_of_root.to_le_bytes());
|
|
bytes[100..104].copy_from_slice(&self.volume_serial_number.to_le_bytes());
|
|
bytes[104..106].copy_from_slice(&self.fs_revision.to_le_bytes());
|
|
bytes[106..108].copy_from_slice(&self.volume_flags.to_le_bytes());
|
|
bytes[108] = self.bytes_per_sector_shift;
|
|
bytes[109] = self.sectors_per_cluster_shift;
|
|
bytes[110] = self.number_of_fats;
|
|
bytes[111] = self.drive_select;
|
|
bytes[112] = self.percent_in_use;
|
|
// bytes[113..120] reserved
|
|
// bytes[120..510] boot_code
|
|
bytes[510..512].copy_from_slice(&self.boot_signature.to_le_bytes());
|
|
|
|
bytes
|
|
}
|
|
}
|
|
|
|
/// Calculate boot checksum for exFAT
|
|
fn calculate_boot_checksum(sectors: &[[u8; 512]; 11]) -> u32 {
|
|
let mut checksum: u32 = 0;
|
|
|
|
for (sector_idx, sector) in sectors.iter().enumerate() {
|
|
for (byte_idx, &byte) in sector.iter().enumerate() {
|
|
// Skip VolumeFlags and PercentInUse fields in boot sector
|
|
if sector_idx == 0 && (byte_idx == 106 || byte_idx == 107 || byte_idx == 112) {
|
|
continue;
|
|
}
|
|
checksum = if checksum & 1 != 0 {
|
|
0x80000000 | (checksum >> 1)
|
|
} else {
|
|
checksum >> 1
|
|
};
|
|
checksum = checksum.wrapping_add(byte as u32);
|
|
}
|
|
}
|
|
|
|
checksum
|
|
}
|
|
|
|
/// Upcase table with Unicode support
|
|
///
|
|
/// Uses the unicode module for proper uppercase conversion
|
|
/// of international characters (Latin Extended, Greek, Cyrillic, etc.)
|
|
fn generate_upcase_table() -> Vec<u8> {
|
|
unicode::generate_upcase_table()
|
|
}
|
|
|
|
/// Calculate upcase table checksum
|
|
fn calculate_upcase_checksum(data: &[u8]) -> u32 {
|
|
let mut checksum: u32 = 0;
|
|
|
|
for &byte in data {
|
|
checksum = if checksum & 1 != 0 {
|
|
0x80000000 | (checksum >> 1)
|
|
} else {
|
|
checksum >> 1
|
|
};
|
|
checksum = checksum.wrapping_add(byte as u32);
|
|
}
|
|
|
|
checksum
|
|
}
|
|
|
|
/// Directory entry types
|
|
const ENTRY_TYPE_VOLUME_LABEL: u8 = 0x83;
|
|
const ENTRY_TYPE_BITMAP: u8 = 0x81;
|
|
const ENTRY_TYPE_UPCASE: u8 = 0x82;
|
|
|
|
/// Create volume label directory entry
|
|
fn create_volume_label_entry(label: &str) -> [u8; 32] {
|
|
let mut entry = [0u8; 32];
|
|
entry[0] = ENTRY_TYPE_VOLUME_LABEL;
|
|
|
|
let label_chars: Vec<u16> = label.encode_utf16().take(11).collect();
|
|
entry[1] = label_chars.len() as u8;
|
|
|
|
for (i, &ch) in label_chars.iter().enumerate() {
|
|
let offset = 2 + i * 2;
|
|
entry[offset..offset + 2].copy_from_slice(&ch.to_le_bytes());
|
|
}
|
|
|
|
entry
|
|
}
|
|
|
|
/// Create bitmap directory entry
|
|
fn create_bitmap_entry(start_cluster: u32, size: u64) -> [u8; 32] {
|
|
let mut entry = [0u8; 32];
|
|
entry[0] = ENTRY_TYPE_BITMAP;
|
|
entry[1] = 0; // BitmapFlags
|
|
// Reserved: bytes 2-19
|
|
entry[20..24].copy_from_slice(&start_cluster.to_le_bytes());
|
|
entry[24..32].copy_from_slice(&size.to_le_bytes());
|
|
entry
|
|
}
|
|
|
|
/// Create upcase table directory entry
|
|
fn create_upcase_entry(start_cluster: u32, size: u64, checksum: u32) -> [u8; 32] {
|
|
let mut entry = [0u8; 32];
|
|
entry[0] = ENTRY_TYPE_UPCASE;
|
|
// Reserved: bytes 1-3
|
|
entry[4..8].copy_from_slice(&checksum.to_le_bytes());
|
|
// Reserved: bytes 8-19
|
|
entry[20..24].copy_from_slice(&start_cluster.to_le_bytes());
|
|
entry[24..32].copy_from_slice(&size.to_le_bytes());
|
|
entry
|
|
}
|
|
|
|
/// Format a partition as exFAT
|
|
pub fn format_exfat<W: Write + Seek>(
|
|
writer: &mut W,
|
|
partition_offset: u64,
|
|
partition_size: u64,
|
|
label: &str,
|
|
) -> Result<()> {
|
|
let volume_sectors = partition_size / 512;
|
|
let cluster_size = get_cluster_size(volume_sectors);
|
|
let _sectors_per_cluster = cluster_size / 512;
|
|
|
|
// Generate volume serial from timestamp
|
|
let serial = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.map(|d| d.as_secs() as u32)
|
|
.unwrap_or(0x12345678);
|
|
|
|
// Create boot sector
|
|
let boot_sector = ExfatBootSector::new(volume_sectors, cluster_size, serial);
|
|
let boot_bytes = boot_sector.to_bytes();
|
|
|
|
// Prepare boot region (12 sectors)
|
|
let mut boot_region: [[u8; 512]; 11] = [[0; 512]; 11];
|
|
boot_region[0] = boot_bytes;
|
|
// Sectors 1-8: Extended boot sectors (can be zero)
|
|
// Sector 9-10: OEM parameters (can be zero)
|
|
|
|
// Calculate boot checksum
|
|
let checksum = calculate_boot_checksum(&boot_region);
|
|
let mut checksum_sector = [0u8; 512];
|
|
for i in 0..128 {
|
|
checksum_sector[i * 4..(i + 1) * 4].copy_from_slice(&checksum.to_le_bytes());
|
|
}
|
|
|
|
// Write main boot region (sectors 0-11)
|
|
writer.seek(SeekFrom::Start(partition_offset))?;
|
|
for sector in &boot_region {
|
|
writer.write_all(sector)?;
|
|
}
|
|
writer.write_all(&checksum_sector)?;
|
|
|
|
// Write backup boot region (sectors 12-23)
|
|
for sector in &boot_region {
|
|
writer.write_all(sector)?;
|
|
}
|
|
writer.write_all(&checksum_sector)?;
|
|
|
|
// Write FAT
|
|
let fat_offset = partition_offset + boot_sector.fat_offset as u64 * 512;
|
|
writer.seek(SeekFrom::Start(fat_offset))?;
|
|
|
|
// Calculate how many clusters the upcase table needs (128KB)
|
|
const UPCASE_TABLE_SIZE: u64 = 128 * 1024;
|
|
let upcase_clusters =
|
|
((UPCASE_TABLE_SIZE + cluster_size as u64 - 1) / cluster_size as u64) as u32;
|
|
let root_cluster = 3 + upcase_clusters; // Root comes after bitmap and upcase
|
|
|
|
// FAT entries: cluster 0 and 1 are reserved
|
|
// 0: Media type (0xFFFFFFF8)
|
|
// 1: Reserved (0xFFFFFFFF)
|
|
// 2: Bitmap cluster (single cluster, end of chain)
|
|
// 3..3+upcase_clusters-1: Upcase table cluster chain
|
|
// 3+upcase_clusters: Root directory cluster (end of chain)
|
|
let mut fat_entries = vec![
|
|
0xFFFFFFF8, // Media type
|
|
0xFFFFFFFF, // Reserved
|
|
0xFFFFFFFF, // Bitmap (single cluster, end of chain)
|
|
];
|
|
|
|
// Build upcase table cluster chain
|
|
for i in 0..upcase_clusters {
|
|
let cluster_num = 3 + i;
|
|
if i == upcase_clusters - 1 {
|
|
// Last cluster in chain
|
|
fat_entries.push(0xFFFFFFFF);
|
|
} else {
|
|
// Point to next cluster
|
|
fat_entries.push(cluster_num + 1);
|
|
}
|
|
}
|
|
|
|
// Root directory (single cluster, end of chain)
|
|
fat_entries.push(0xFFFFFFFF);
|
|
|
|
for entry in &fat_entries {
|
|
writer.write_all(&entry.to_le_bytes())?;
|
|
}
|
|
|
|
// Zero fill rest of FAT
|
|
let fat_remaining = (boot_sector.fat_length as usize * 512) - (fat_entries.len() * 4);
|
|
writer.write_all(&vec![0u8; fat_remaining])?;
|
|
|
|
// Calculate cluster heap offset
|
|
let heap_offset = partition_offset + boot_sector.cluster_heap_offset as u64 * 512;
|
|
|
|
// Cluster 2: Allocation Bitmap
|
|
let bitmap_size = (boot_sector.cluster_count + 7) / 8;
|
|
let _bitmap_clusters =
|
|
((bitmap_size as u64 + cluster_size as u64 - 1) / cluster_size as u64).max(1);
|
|
let mut bitmap = vec![0u8; cluster_size as usize];
|
|
|
|
// Mark clusters 2, 3..3+upcase_clusters-1, root_cluster as used
|
|
// Cluster 2: bitmap
|
|
bitmap[0] |= 0b00000100; // Bit 2
|
|
// Clusters 3..3+upcase_clusters-1: upcase table
|
|
for i in 0..upcase_clusters {
|
|
let cluster = 3 + i;
|
|
let byte_idx = (cluster / 8) as usize;
|
|
let bit_idx = cluster % 8;
|
|
if byte_idx < bitmap.len() {
|
|
bitmap[byte_idx] |= 1 << bit_idx;
|
|
}
|
|
}
|
|
// Root directory cluster
|
|
let byte_idx = (root_cluster / 8) as usize;
|
|
let bit_idx = root_cluster % 8;
|
|
if byte_idx < bitmap.len() {
|
|
bitmap[byte_idx] |= 1 << bit_idx;
|
|
}
|
|
|
|
writer.seek(SeekFrom::Start(heap_offset))?;
|
|
writer.write_all(&bitmap)?;
|
|
|
|
// Cluster 3..3+upcase_clusters-1: Upcase table
|
|
let upcase_data = generate_upcase_table();
|
|
let upcase_checksum = calculate_upcase_checksum(&upcase_data);
|
|
let upcase_offset = heap_offset + cluster_size as u64; // Start at cluster 3
|
|
writer.seek(SeekFrom::Start(upcase_offset))?;
|
|
writer.write_all(&upcase_data)?;
|
|
|
|
// Pad to fill all upcase clusters
|
|
let upcase_total_size = upcase_clusters as usize * cluster_size as usize;
|
|
let upcase_padding = upcase_total_size - upcase_data.len();
|
|
if upcase_padding > 0 {
|
|
writer.write_all(&vec![0u8; upcase_padding])?;
|
|
}
|
|
|
|
// Root directory cluster
|
|
let root_offset = heap_offset + (1 + upcase_clusters as u64) * cluster_size as u64;
|
|
writer.seek(SeekFrom::Start(root_offset))?;
|
|
|
|
// Write directory entries
|
|
let volume_label_entry = create_volume_label_entry(label);
|
|
let bitmap_entry = create_bitmap_entry(2, bitmap_size as u64);
|
|
let upcase_entry = create_upcase_entry(3, upcase_data.len() as u64, upcase_checksum);
|
|
|
|
writer.write_all(&volume_label_entry)?;
|
|
writer.write_all(&bitmap_entry)?;
|
|
writer.write_all(&upcase_entry)?;
|
|
|
|
// Pad root directory to cluster size
|
|
let root_used = 32 * 3;
|
|
let root_padding = cluster_size as usize - root_used;
|
|
writer.write_all(&vec![0u8; root_padding])?;
|
|
|
|
writer.flush()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_cluster_size() {
|
|
// < 256MB: 4KB clusters
|
|
assert_eq!(get_cluster_size(200 * 2048), 4096); // 100MB → 4KB
|
|
assert_eq!(get_cluster_size(100 * 1024 * 1024 / 512), 4096); // 100MB → 4KB
|
|
|
|
// 256MB - 8GB: 32KB clusters
|
|
assert_eq!(get_cluster_size(512 * 1024 * 1024 / 512), 32768); // 512MB → 32KB
|
|
assert_eq!(get_cluster_size(4 * 1024 * 1024 * 1024 / 512), 32768); // 4GB → 32KB
|
|
|
|
// >= 8GB: 128KB clusters
|
|
assert_eq!(get_cluster_size(8 * 1024 * 1024 * 1024 / 512), 131072); // 8GB → 128KB
|
|
assert_eq!(get_cluster_size(16 * 1024 * 1024 * 1024 / 512), 131072); // 16GB → 128KB
|
|
}
|
|
}
|