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;i0&&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(()=>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(()=>volume.Delete("X:\\Disks\\Data"));Assert.Throws(()=>volume.WriteBytes("X:\\Disks\\Data\\huge.bin",new byte[4*1024*1024+1])); Assert.Throws(()=>volume.ReadText("X:\\Disks\\Other\\file.txt"));Assert.Throws(()=>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(()=>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(()=>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(()=>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(()=>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(()=>stream.Write(new byte[2]));Assert.Throws(()=>stream.Position=-1);Assert.Throws(()=>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(()=>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);} }