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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Security.Cryptography;
using DiscUtils.Ntfs;
using XFESystem.Core;
using XFESystem.Storage;
using XFESystem.StorageFixtures;
using Xunit;

namespace XFESystem.Storage.Tests;

public sealed class PhysicalVolumeTests : IDisposable
{
    readonly string directory=Path.Combine(Path.GetTempPath(),"XfeStorageTests-"+Guid.NewGuid().ToString("N"));
    public PhysicalVolumeTests()=>Directory.CreateDirectory(directory);
    FixtureDisk Create(string format,string name="Data")
    {
        var disk=new FixtureDisk(Path.Combine(directory,Guid.NewGuid().ToString("N")+".img"),name,true);
        disk.Format(disk.Gpt(),format,[1,2,3,4]);return disk;
    }
    static DiskPartition Part(FixtureDisk disk)=>Assert.Single(DiskPartitions.Read(disk));
    static byte[] Sector(FixtureDisk disk,ulong lba){byte[] b=new byte[512];disk.Read(lba,b);return b;}
    static MountedVolume Mount(FixtureDisk disk)=>new(disk,Part(disk),"X:\\Disks\\Data");
    [Theory][InlineData("NTFS")][InlineData("FAT")][InlineData("exFAT")]
    public void ActualFilesystemReadsUnicodeDirectoriesResidentAndLargeData(string format)
    {
        using var disk=Create(format);using var volume=Mount(disk);
        Assert.True(volume.Writable,volume.ReadOnlyReason);Assert.Equal("Data_MEDIA_READ_OK",volume.ReadText("X:\\Disks\\Data\\Readme.txt"));
        Assert.Equal("真实磁盘中文文件",volume.ReadText("X:\\Disks\\Data\\目录\\中文.txt"));
        Assert.Contains(volume.List("X:\\Disks\\Data"),e=>e.Name=="Programs"&&e.IsDirectory);
        byte[] pattern=volume.ReadBytes("X:\\Disks\\Data\\Pattern.bin");Assert.Equal(196608,pattern.Length);
        for(int i=0;i<pattern.Length;i++)Assert.Equal(unchecked((byte)(i*37+11)),pattern[i]);
        Assert.Equal(0,disk.Writes);
    }
    [Theory][InlineData("NTFS")][InlineData("FAT")][InlineData("exFAT")]
    public void CreateModifyMoveDeleteAndBinarySurviveFreshFilesystem(string format)
    {
        using var disk=Create(format);byte[] mbr=Sector(disk,0),gpt=Sector(disk,1),tail=Sector(disk,disk.SectorCount-1);var partition=Part(disk);
        using(var volume=Mount(disk))
        {
            volume.CreateDirectory("X:\\Disks\\Data\\Work\\Nested");volume.WriteText("X:\\Disks\\Data\\Work\\Nested\\New.txt","first");
            volume.WriteText("X:\\Disks\\Data\\EditMe.txt","after\n中文");volume.Move("X:\\Disks\\Data\\Work\\Nested\\New.txt","X:\\Disks\\Data\\Work\\Nested\\Renamed.txt");
            volume.WriteBytes("X:\\Disks\\Data\\Work\\Program.dll",[6,7,8,9]);volume.Delete("X:\\Disks\\Data\\DeleteMe.txt");
        }
        using(var reopened=Mount(disk))
        {
            Assert.True(reopened.Writable,reopened.ReadOnlyReason);Assert.Equal("after\n中文",reopened.ReadText("X:\\Disks\\Data\\EditMe.txt"));
            Assert.Equal("first",reopened.ReadText("X:\\Disks\\Data\\Work\\Nested\\Renamed.txt"));Assert.False(reopened.Exists("X:\\Disks\\Data\\DeleteMe.txt"));
            Assert.Equal(new byte[]{6,7,8,9},reopened.ReadBytes("X:\\Disks\\Data\\Work\\Program.dll"));
        }
        Assert.True(disk.Writes>0&&disk.Flushes>0);Assert.All(disk.Written,write=>Assert.True(write.Lba>=partition.Start&&(ulong)write.Bytes/512<=partition.Count-(write.Lba-partition.Start)));
        Assert.Equal(mbr,Sector(disk,0));Assert.Equal(gpt,Sector(disk,1));Assert.Equal(tail,Sector(disk,disk.SectorCount-1));
    }
    [Theory][InlineData("NTFS")][InlineData("FAT")][InlineData("exFAT")]
    public void ProtectedPartitionsAndDeviceReadOnlyCannotWrite(string format)
    {
        using var disk=Create(format);using var volume=new MountedVolume(disk,Part(disk) with{DataPartition=false},"X:\\Disks\\Data");
        Assert.False(volume.Writable);Assert.Throws<UnauthorizedAccessException>(()=>volume.WriteText("X:\\Disks\\Data\\EditMe.txt","forbidden"));Assert.Equal(0,disk.Writes);
        disk.CanWrite=false;using var locked=Mount(disk);Assert.False(locked.Writable);Assert.Equal("before",locked.ReadText("X:\\Disks\\Data\\EditMe.txt"));Assert.Equal(0,disk.Writes);
    }
    [Theory][InlineData("NTFS")][InlineData("FAT")][InlineData("exFAT")]
    public void OversizedFilesRootDeletionAndPathEscapeAreRejectedBeforeWrites(string format)
    {
        using var disk=Create(format);using var volume=Mount(disk);
        Assert.Throws<IOException>(()=>volume.Delete("X:\\Disks\\Data"));Assert.Throws<IOException>(()=>volume.WriteBytes("X:\\Disks\\Data\\huge.bin",new byte[4*1024*1024+1]));
        Assert.Throws<IOException>(()=>volume.ReadText("X:\\Disks\\Other\\file.txt"));Assert.Throws<ArgumentException>(()=>volume.Delete("X:\\Disks\\Data\\..\\file.txt"));Assert.Equal(0,disk.Writes);
    }
    [Fact] public void NtfsHibernationRefusesWriteWithoutRemovingHibernationFile()
    {
        using var disk=Create("NTFS");using(var volume=Mount(disk))volume.WriteBytes("X:\\Disks\\Data\\hiberfil.sys","hibr"u8);
        int written=disk.Writes;using var reopened=Mount(disk);Assert.False(reopened.Writable);Assert.Contains("hibernated",reopened.ReadOnlyReason);Assert.Equal(written,disk.Writes);
        Assert.Throws<UnauthorizedAccessException>(()=>reopened.Delete("X:\\Disks\\Data\\hiberfil.sys"));
    }
    [Fact] public void FailedNtfsWriteRetainsDirtyFlagAndDisablesFurtherWrites()
    {
        using var disk=Create("NTFS");using var volume=Mount(disk);
        // Fail the first data-sector write, after the primary/mirror dirty
        // records have reached media. Existing file content is not overwritten.
        disk.FailAfterWrites=2;
        Assert.ThrowsAny<IOException>(()=>volume.WriteText("X:\\Disks\\Data\\EditMe.txt","failure"));Assert.False(volume.Writable);
        disk.FailAfterWrites=null;using var reopened=Mount(disk);Assert.False(reopened.Writable);Assert.Contains("recovery",reopened.ReadOnlyReason);
    }
    [Fact] public void GptCrcAndExtendedPartitionCycleAreRejectedWithoutWrites()
    {
        using var disk=new FixtureDisk(Path.Combine(directory,"gpt.img"),"Raw",true);disk.Gpt();byte[] header=Sector(disk,1);header[16]^=1;disk.Raw(1,header);Assert.Throws<IOException>(()=>DiskPartitions.Read(disk));
        using var extended=new FixtureDisk(Path.Combine(directory,"ebr.img"),"Raw",true,256L*1024*1024);extended.ExtendedMbr();Assert.Equal(new[]{5,6},DiskPartitions.Read(extended).Select(p=>p.Number));
        byte[] ebr=Sector(extended,249856);ebr[466]=15;System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(ebr.AsSpan(470),247808);extended.Raw(249856,ebr);Assert.Throws<IOException>(()=>DiskPartitions.Read(extended));Assert.Equal(0,extended.Writes);
    }
    [Fact] public void PartitionStreamPreservesAdjacentBytesAndRejectsOverrun()
    {
        using var disk=new FixtureDisk(Path.Combine(directory,"stream.img"),"Raw",true);var part=disk.Gpt();
        using var stream=new PartitionStream(disk,part,true);stream.Position=511;stream.Write(new byte[]{1,2,3});stream.Position=510;byte[] bytes=new byte[5];stream.ReadExactly(bytes);Assert.Equal(new byte[]{0,1,2,3,0},bytes);
        stream.Position=stream.Length-1;Assert.Throws<IOException>(()=>stream.Write(new byte[2]));Assert.Throws<IOException>(()=>stream.Position=-1);Assert.Throws<NotSupportedException>(()=>stream.SetLength(1));
    }
    [Fact] public void NamespaceOverlayPreservesWorkspaceAndCrossVolumeFileMove()
    {
        using var disk=Create("NTFS","NVMe0");var memory=new MemoryWorkspace();memory.WriteText(SystemPaths.Combine(SystemPaths.Documents,"Local.txt"),"local");
        var workspace=new MountedWorkspace(memory,[disk]);string target="X:\\Disks\\NVMe0P1\\Moved.txt";
        Assert.Contains(workspace.List(SystemPaths.Root),e=>e.Path==MountedWorkspace.DisksPath);workspace.Move(SystemPaths.Combine(SystemPaths.Documents,"Local.txt"),target);
        Assert.Equal("local",workspace.ReadText(target));Assert.False(memory.Exists(SystemPaths.Combine(SystemPaths.Documents,"Local.txt")));Assert.Contains("Read/write",workspace.ReadText("X:\\Disks\\MountedVolumes.txt"));
        Assert.Throws<IOException>(()=>workspace.Delete(MountedWorkspace.DisksPath));
    }
    [Fact] public void Native4096ByteGptUsesLogicalLbasAndPreservesBothTables()
    {
        using var disk=new FixtureDisk(Path.Combine(directory,"4kn.img"),"Data",true,logicalSectorBytes:4096);
        var partition=disk.Gpt();Assert.Equal(partition,Part(disk));Assert.Equal(0UL,partition.Start%8);Assert.Equal(0UL,partition.Count%8);
        disk.Format(partition,"NTFS",[1,2,3]);byte[] primary=Sector(disk,8),backup=Sector(disk,disk.SectorCount-8);
        using(var volume=Mount(disk)){Assert.True(volume.Writable,volume.ReadOnlyReason);volume.WriteText("X:\\Disks\\Data\\EditMe.txt","4Kn metadata and file write");}
        using(var volume=Mount(disk)){Assert.True(volume.Writable);Assert.Equal("4Kn metadata and file write",volume.ReadText("X:\\Disks\\Data\\EditMe.txt"));}
        Assert.Equal(primary,Sector(disk,8));Assert.Equal(backup,Sector(disk,disk.SectorCount-8));
    }
    [Fact] public void ExistingDiskReportFileIsPreserved()
    {
        var memory=new MemoryWorkspace(false);memory.CreateDirectory(MountedWorkspace.DisksPath);
        memory.WriteText("X:\\Disks\\MountedVolumes.txt","user data");var workspace=new MountedWorkspace(memory,[]);
        Assert.Equal("user data",workspace.ReadText("X:\\Disks\\MountedVolumes.txt"));Assert.Contains("Mounted disks",workspace.ReadText("X:\\Disks\\MountedVolumes-1.txt"));
    }
    [Fact] public void ExFatNestedNamesAreRealDirectoryEntriesWithEitherSeparator()
    {
        using var disk=Create("exFAT");
        using(var volume=Mount(disk)){volume.CreateDirectory("X:\\Disks\\Data\\Portable");volume.WriteText("X:\\Disks\\Data\\Portable\\Saved.txt","portable");}
        using var raw=new PartitionStream(disk,Part(disk),false);
        using var fs=new DiscUtils.ExFat.ExFatFileSystem(raw,['/']);
        Assert.True(fs.FileExists("Portable/Saved.txt"));Assert.False(fs.FileExists("Portable\\Saved.txt"));
        using var file=fs.OpenFile("Portable/Saved.txt",FileMode.Open,FileAccess.Read);using var reader=new StreamReader(file);Assert.Equal("portable",reader.ReadToEnd());
    }
    public void Dispose(){foreach(string file in Directory.EnumerateFiles(directory))File.Delete(file);Directory.Delete(directory);}
}