XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Buffers.Binary;
using System.Text;
using XFESystem.Core;

namespace XFESystem.Storage;

public interface IPhysicalDisk
{
    string Name { get; }
    ulong SectorCount { get; }
    int LogicalSectorBytes => 512;
    bool CanWrite { get; }
    void Read(ulong lba, Span<byte> bytes);
    void Write(ulong lba, ReadOnlySpan<byte> bytes);
    void Flush();
    void AuthorizePartition(ulong start, ulong count);
}
public readonly record struct DiskPartition(int Number, ulong Start, ulong Count, bool DataPartition, string Name);

public static class DiskPartitions
{
    static uint U32(byte[] b,int at)=>BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(at));
    static ulong U64(byte[] b,int at)=>BinaryPrimitives.ReadUInt64LittleEndian(b.AsSpan(at));
    static ulong Sectors(IPhysicalDisk disk)=>disk.SectorCount/(ulong)(disk.LogicalSectorBytes/512);
    static DiskPartition[] Normalize(IEnumerable<DiskPartition> partitions,IPhysicalDisk disk)
    {ulong unit=(ulong)(disk.LogicalSectorBytes/512);return partitions.Select(p=>p with{Start=checked(p.Start*unit),Count=checked(p.Count*unit)}).ToArray();}
    static byte[] Read(IPhysicalDisk disk,ulong lba,int count=1)
    {
        if(lba>=Sectors(disk)||(ulong)count>Sectors(disk)-lba)throw new IOException("Partition metadata leaves its disk.");
        byte[] bytes=new byte[checked(count*disk.LogicalSectorBytes)];disk.Read(lba*(ulong)(disk.LogicalSectorBytes/512),bytes);return bytes;
    }
    public static DiskPartition[] Read(IPhysicalDisk disk)
    {
        if(disk.LogicalSectorBytes is not(512 or 4096)||disk.SectorCount%(ulong)(disk.LogicalSectorBytes/512)!=0)throw new IOException("Unsupported disk sector geometry.");
        byte[] mbr=Read(disk,0);if(mbr[510]!=85||mbr[511]!=170)return [];
        var result=new List<DiskPartition>();
        if(Enumerable.Range(0,4).Any(i=>mbr[446+i*16+4]==0xee))return Normalize(Gpt(disk),disk);
        var containers=new List<(ulong Start,ulong Count)>();
        for(int i=0;i<4;i++)
        {
            int at=446+i*16;byte type=mbr[at+4];ulong start=U32(mbr,at+8),count=U32(mbr,at+12);
            if(type==0||count==0)continue;Bounds(disk,start,count);
            if(type is 5 or 15 or 133){containers.Add((start,count));continue;}
            result.Add(new(i+1,start,count,type is 7 or 6 or 11 or 12 or 14,""));
        }
        Validate(result.Concat(containers.Select(c=>new DiskPartition(0,c.Start,c.Count,false,""))).ToList());
        int number=5;var ebrSectors=new List<ulong>();
        foreach(var container in containers)
        {
            ulong current=container.Start;var seen=new HashSet<ulong>();
            for(int limit=0;limit<128;limit++)
            {
                if(!seen.Add(current)||current<container.Start||current-container.Start>=container.Count)throw new IOException("Cyclic or invalid extended partition chain.");
                ebrSectors.Add(current);
                byte[] ebr=Read(disk,current);if(ebr[510]!=85||ebr[511]!=170)throw new IOException("Invalid extended partition signature.");
                byte type=ebr[450];ulong relative=U32(ebr,454),count=U32(ebr,458);
                if(type!=0&&count!=0)
                {
                    if(relative==0||relative>ulong.MaxValue-current)throw new IOException("Invalid logical partition offset.");
                    ulong start=current+relative;Bounds(disk,start,count);
                    if(start<container.Start||start-container.Start>=container.Count||count>container.Count-(start-container.Start))throw new IOException("Logical partition leaves its container.");
                    result.Add(new(number++,start,count,type is 7 or 6 or 11 or 12 or 14,""));
                }
                byte linkType=ebr[466];ulong link=U32(ebr,470);
                if(linkType==0&&link==0)break;
                if(linkType is not(5 or 15 or 133)||link==0||link>=container.Count||limit==127)throw new IOException("Invalid extended partition link.");
                current=container.Start+link;
            }
        }
        Validate(result);
        if(ebrSectors.Any(lba=>result.Any(p=>lba>=p.Start&&lba-p.Start<p.Count)))throw new IOException("An extended partition record overlaps file data.");
        return Normalize(result,disk);
    }
    static DiskPartition[] Gpt(IPhysicalDisk disk)
    {
        ulong sectors=Sectors(disk);byte[] header=Read(disk,1);Header(header,1,sectors-1,sectors);
        uint count=U32(header,80),size=U32(header,84);
        if(count==0||count>128||size<128||size>1024||(size&7)!=0)throw new IOException("Unsupported GPT entry geometry.");
        int bytes=checked((int)(count*size)),tableSectors=(bytes+disk.LogicalSectorBytes-1)/disk.LogicalSectorBytes;
        byte[] table=Read(disk,U64(header,72),tableSectors);
        if(UsbWorkspaceFormat.Crc(table.AsSpan(0,bytes))!=U32(header,88))throw new IOException("GPT partition CRC mismatch.");
        byte[] backup=Read(disk,sectors-1);Header(backup,sectors-1,1,sectors);
        if(!header.AsSpan(56,16).SequenceEqual(backup.AsSpan(56,16))||U32(backup,80)!=count||U32(backup,84)!=size||U32(backup,88)!=U32(header,88)||
           !Read(disk,U64(backup,72),tableSectors).AsSpan(0,bytes).SequenceEqual(table.AsSpan(0,bytes)))throw new IOException("GPT primary and backup disagree.");
        ulong first=U64(header,40),last=U64(header,48);
        ulong primaryTable=U64(header,72),backupTable=U64(backup,72);
        if(first<2+(ulong)tableSectors||last<first||last>=sectors-1||first!=U64(backup,40)||last!=U64(backup,48)||
           primaryTable<2||primaryTable>=first||(ulong)tableSectors>first-primaryTable||
           backupTable<=last||backupTable>=sectors-1||(ulong)tableSectors>sectors-1-backupTable)throw new IOException("Invalid GPT usable range or table placement.");
        var result=new List<DiskPartition>();var ids=new HashSet<Guid>();
        for(int i=0;i<count;i++)
        {
            int at=checked(i*(int)size);var type=new Guid(table.AsSpan(at,16));if(type==Guid.Empty)continue;
            var id=new Guid(table.AsSpan(at+16,16));ulong start=U64(table,at+32),end=U64(table,at+40);
            if(id==Guid.Empty||!ids.Add(id)||start<first||end<start||end>last)throw new IOException("Invalid GPT partition identity or range.");
            bool data=type==new Guid("ebd0a0a2-b9e5-4433-87c0-68b6b72699c7")&&(U64(table,at+48)&(1UL<<60))==0;
            result.Add(new(i+1,start,end-start+1,data,Encoding.Unicode.GetString(table,at+56,72).TrimEnd('\0')));
        }
        Validate(result);return result.ToArray();
    }
    static void Header(byte[] header,ulong current,ulong other,ulong sectors)
    {
        uint size=U32(header,12);if(!header.AsSpan(0,8).SequenceEqual("EFI PART"u8)||U32(header,8)!=65536||size<92||size>header.Length||U32(header,20)!=0||U64(header,24)!=current||U64(header,32)!=other||other>=sectors)throw new IOException("Invalid GPT header.");
        uint crc=U32(header,16);byte[] copy=header.AsSpan(0,(int)size).ToArray();copy.AsSpan(16,4).Clear();
        if(UsbWorkspaceFormat.Crc(copy)!=crc)throw new IOException("GPT header CRC mismatch.");
    }
    static void Bounds(IPhysicalDisk disk,ulong start,ulong count)
    {if(start==0||count==0||start>=Sectors(disk)||count>Sectors(disk)-start)throw new IOException("Partition leaves its disk.");}
    static void Validate(List<DiskPartition> parts)
    {
        var sorted=parts.OrderBy(p=>p.Start).ToArray();
        for(int i=1;i<sorted.Length;i++)if(sorted[i].Start-sorted[i-1].Start<sorted[i-1].Count)throw new IOException("Overlapping partitions were refused.");
    }
}