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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Buffers.Binary;
using DiscUtils.Ntfs;

namespace XFESystem.Storage;

// DiscUtils performs format-aware metadata operations. Bracket each mutation
// with the filesystem's dirty flag and device-cache barriers. This is not an
// implementation of Windows $LogFile replay or an atomic power-loss journal.
internal sealed class VolumeDirtyState
{
    readonly PartitionStream stream;
    readonly string kind;
    readonly long[] offsets;
    readonly int recordBytes, sectorBytes;
    public bool IsDirty { get; }
    VolumeDirtyState(PartitionStream stream,string kind,long[] offsets,int recordBytes,int sectorBytes,bool dirty)
    {this.stream=stream;this.kind=kind;this.offsets=offsets;this.recordBytes=recordBytes;this.sectorBytes=sectorBytes;IsDirty=dirty;}
    static ushort U16(byte[] b,int at)=>BinaryPrimitives.ReadUInt16LittleEndian(b.AsSpan(at));
    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 void W16(byte[] b,int at,ushort value)=>BinaryPrimitives.WriteUInt16LittleEndian(b.AsSpan(at),value);
    static byte[] Read(Stream stream,long offset,int count){stream.Position=offset;byte[] b=new byte[count];stream.ReadExactly(b);return b;}
    public static VolumeDirtyState Create(PartitionStream stream,string kind,NtfsFileSystem? ntfs)
    {
        byte[] boot=Read(stream,0,512);
        if(kind=="NTFS")
        {
            int sector=U16(boot,11),cluster=checked(sector*boot[13]);int encoded=(sbyte)boot[64];
            int record=encoded<0&&encoded>=-16?1<<-encoded:encoded>0?checked(encoded*cluster):0;
            if(sector is not(512 or 4096)||record<sector||record>65536||record%sector!=0)throw new IOException("Unsupported NTFS record geometry.");
            var first=ntfs!.PathToExtents("$MFT").First();
            if(first.Start<0||first.Length<4L*record||first.Start>stream.Length-4L*record)throw new IOException("The initial NTFS MFT records are not in a supported extent.");
            long mirror=checked((long)U64(boot,56)*cluster);
            if(mirror<0||mirror>stream.Length-4L*record)throw new IOException("NTFS mirror leaves its partition.");
            long[] positions=[first.Start+3L*record,mirror+3L*record];bool dirty=false;
            foreach(long at in positions){byte[] raw=Read(stream,at,record);int flags=NtfsFlags(raw,sector);dirty|=(U16(raw,flags)&1)!=0;}
            return new(stream,kind,positions,record,sector,dirty);
        }
        if(kind=="exFAT")
        {
            int sector=1<<boot[108];if(sector is not(512 or 4096))throw new IOException("Unsupported exFAT sector size.");
            byte[] backup=Read(stream,12L*sector,512);if(!backup.AsSpan(3,8).SequenceEqual("EXFAT   "u8))throw new IOException("Invalid exFAT backup boot sector.");
            return new(stream,kind,[0,12L*sector],512,sector,((U16(boot,106)|U16(backup,106))&2)!=0);
        }
        int bytesPerSector=U16(boot,11),reserved=U16(boot,14),fats=boot[16];
        uint fatSectors=U16(boot,22);bool fat32=fatSectors==0;if(fat32)fatSectors=U32(boot,36);
        if(bytesPerSector is not(512 or 4096)||reserved==0||fats<1||fats>2||fatSectors==0)throw new IOException("Unsupported FAT geometry.");
        if(!fat32)
        {
            uint total=U16(boot,19);if(total==0)total=U32(boot,32);
            uint roots=(uint)((U16(boot,17)*32+bytesPerSector-1)/bytesPerSector);
            if(boot[13]==0||total<(uint)reserved+fats*fatSectors+roots)throw new IOException("Invalid FAT data range.");
            if((total-(uint)reserved-(uint)fats*fatSectors-roots)/boot[13]<4085)
                return new(stream,"FAT12",[],512,bytesPerSector,false);
        }
        long[] fatOffsets=Enumerable.Range(0,fats).Select(i=>((long)reserved+i*fatSectors)*bytesPerSector).ToArray();bool unclean=false;
        foreach(long at in fatOffsets){byte[] b=Read(stream,at,512);unclean|=fat32?(U32(b,4)&0x08000000)==0:(U16(b,2)&0x8000)==0;}
        return new(stream,fat32?"FAT32":"FAT16",fatOffsets,512,bytesPerSector,unclean);
    }
    // Restores the USA trailers before examining attributes. The returned
    // buffer is deprotected until Protect is called before the sector write.
    static int NtfsFlags(byte[] raw,int sector)
    {
        if(!raw.AsSpan(0,4).SequenceEqual("FILE"u8))throw new IOException("Invalid NTFS volume record.");
        int usa=U16(raw,4),count=U16(raw,6);
        if(count!=raw.Length/sector+1||usa<8||usa+count*2>raw.Length)throw new IOException("Invalid NTFS record fixup range.");
        ushort sequence=U16(raw,usa);
        for(int i=1;i<count;i++){int end=i*sector-2;if(U16(raw,end)!=sequence)throw new IOException("Torn NTFS volume record.");W16(raw,end,U16(raw,usa+i*2));}
        int used=checked((int)U32(raw,24)),at=U16(raw,20);
        if(used<at||used>raw.Length)throw new IOException("Invalid NTFS record length.");
        for(int budget=0;budget<128&&at+8<=used;budget++)
        {
            uint type=U32(raw,at);if(type==0xffffffff)break;int length=checked((int)U32(raw,at+4));
            if(length<24||length>used-at)throw new IOException("Invalid NTFS attribute bounds.");
            if(type==0x70)
            {
                int offset=U16(raw,at+20);uint size=U32(raw,at+16);
                if(raw[at+8]!=0||raw[at+9]!=0||size<12||offset<24||offset+size>length)throw new IOException("Invalid NTFS volume-information attribute.");
                return at+offset+10;
            }
            at+=length;
        }
        throw new IOException("The NTFS volume-information attribute is missing.");
    }
    static void Protect(byte[] raw,int sector)
    {
        int usa=U16(raw,4),count=U16(raw,6);ushort sequence=unchecked((ushort)(U16(raw,usa)+1));if(sequence==0)sequence=1;W16(raw,usa,sequence);
        for(int i=1;i<count;i++){int end=i*sector-2;W16(raw,usa+i*2,U16(raw,end));W16(raw,end,sequence);}
    }
    public void Set(bool dirty)
    {
        var patches=new List<(long Offset,byte[] Bytes)>();
        foreach(long at in offsets)
        {
            byte[] bytes=Read(stream,at,recordBytes);
            if(kind=="NTFS")
            {int flags=NtfsFlags(bytes,sectorBytes);ushort value=U16(bytes,flags);W16(bytes,flags,dirty?(ushort)(value|1):(ushort)(value&~1));Protect(bytes,sectorBytes);}
            else if(kind=="exFAT")
            {ushort value=U16(bytes,106);W16(bytes,106,dirty?(ushort)(value|2):(ushort)(value&~2));}
            else if(kind=="FAT32")
            {uint value=U32(bytes,4);BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(4),dirty?value&~0x08000000u:value|0x08000000u);}
            else {ushort value=U16(bytes,2);W16(bytes,2,dirty?(ushort)(value&~0x8000):(ushort)(value|0x8000));}
            patches.Add((at,bytes));
        }
        foreach(var patch in patches){stream.Position=patch.Offset;stream.Write(patch.Bytes);}
        stream.Flush();
    }
}