namespace XFESystem.Storage;
// Every seek and unaligned metadata write remains inside one validated
// partition. A filesystem never receives a whole-device stream.
public sealed class PartitionStream : Stream
{
readonly IPhysicalDisk disk;
readonly DiskPartition partition;
readonly bool writable;
long position;
public PartitionStream(IPhysicalDisk disk,DiskPartition partition,bool writable)
{
this.disk=disk;this.partition=partition;this.writable=writable&&disk.CanWrite&&partition.DataPartition;
if(partition.Start>=disk.SectorCount||partition.Count==0||partition.Count>disk.SectorCount-partition.Start||partition.Count>(ulong)long.MaxValue/512)throw new IOException("Invalid partition stream extent.");
}
public override bool CanRead=>true;
public override bool CanSeek=>true;
public override bool CanWrite=>writable;
public override long Length=>checked((long)partition.Count*512);
public override long Position {get=>position;set{if(value<0||value>Length)throw new IOException("Seek leaves partition.");position=value;}}
public override long Seek(long offset,SeekOrigin origin)
{Position=checked((origin switch {SeekOrigin.Begin=>0L,SeekOrigin.Current=>position,SeekOrigin.End=>Length,_=>throw new ArgumentException("Invalid seek origin.")})+offset);return position;}
public override void Flush()=>disk.Flush();
public override void SetLength(long value)=>throw new NotSupportedException("Partition resizing is not supported.");
public override int Read(byte[] buffer,int offset,int count)=>Read(buffer.AsSpan(offset,count));
public override int Read(Span<byte> buffer)
{
int total=(int)Math.Min(buffer.Length,Length-position),done=0;
while(done<total)
{
int skip=(int)(position&511),copy=Math.Min(total-done,32768-skip),bytes=(skip+copy+511)&~511;
byte[] sectors=new byte[bytes];disk.Read(partition.Start+(ulong)position/512,sectors);
sectors.AsSpan(skip,copy).CopyTo(buffer[done..]);position+=copy;done+=copy;
}
return done;
}
public override void Write(byte[] buffer,int offset,int count)=>Write(buffer.AsSpan(offset,count));
public override void Write(ReadOnlySpan<byte> buffer)
{
if(!writable)throw new UnauthorizedAccessException("This volume is read-only.");
if(buffer.Length>Length-position)throw new IOException("Write leaves partition.");
while(!buffer.IsEmpty)
{
int skip=(int)(position&511),copy=Math.Min(buffer.Length,32768-skip),bytes=(skip+copy+511)&~511;
byte[] sectors=new byte[bytes];ulong lba=partition.Start+(ulong)position/512;
if(skip!=0||copy!=bytes)disk.Read(lba,sectors);
buffer[..copy].CopyTo(sectors.AsSpan(skip));disk.AuthorizePartition(partition.Start,partition.Count);disk.Write(lba,sectors);
position+=copy;buffer=buffer[copy..];
}
}
}
namespace XFESystem.Storage;
// Every seek and unaligned metadata write remains inside one validated
// partition. A filesystem never receives a whole-device stream.
public sealed class PartitionStream : Stream
{
readonly IPhysicalDisk disk;
readonly DiskPartition partition;
readonly bool writable;
long position;
public PartitionStream(IPhysicalDisk disk,DiskPartition partition,bool writable)
{
this.disk=disk;this.partition=partition;this.writable=writable&&disk.CanWrite&&partition.DataPartition;
if(partition.Start>=disk.SectorCount||partition.Count==0||partition.Count>disk.SectorCount-partition.Start||partition.Count>(ulong)long.MaxValue/512)throw new IOException("Invalid partition stream extent.");
}
public override bool CanRead=>true;
public override bool CanSeek=>true;
public override bool CanWrite=>writable;
public override long Length=>checked((long)partition.Count*512);
public override long Position {get=>position;set{if(value<0||value>Length)throw new IOException("Seek leaves partition.");position=value;}}
public override long Seek(long offset,SeekOrigin origin)
{Position=checked((origin switch {SeekOrigin.Begin=>0L,SeekOrigin.Current=>position,SeekOrigin.End=>Length,_=>throw new ArgumentException("Invalid seek origin.")})+offset);return position;}
public override void Flush()=>disk.Flush();
public override void SetLength(long value)=>throw new NotSupportedException("Partition resizing is not supported.");
public override int Read(byte[] buffer,int offset,int count)=>Read(buffer.AsSpan(offset,count));
public override int Read(Span<byte> buffer)
{
int total=(int)Math.Min(buffer.Length,Length-position),done=0;
while(done<total)
{
int skip=(int)(position&511),copy=Math.Min(total-done,32768-skip),bytes=(skip+copy+511)&~511;
byte[] sectors=new byte[bytes];disk.Read(partition.Start+(ulong)position/512,sectors);
sectors.AsSpan(skip,copy).CopyTo(buffer[done..]);position+=copy;done+=copy;
}
return done;
}
public override void Write(byte[] buffer,int offset,int count)=>Write(buffer.AsSpan(offset,count));
public override void Write(ReadOnlySpan<byte> buffer)
{
if(!writable)throw new UnauthorizedAccessException("This volume is read-only.");
if(buffer.Length>Length-position)throw new IOException("Write leaves partition.");
while(!buffer.IsEmpty)
{
int skip=(int)(position&511),copy=Math.Min(buffer.Length,32768-skip),bytes=(skip+copy+511)&~511;
byte[] sectors=new byte[bytes];ulong lba=partition.Start+(ulong)position/512;
if(skip!=0||copy!=bytes)disk.Read(lba,sectors);
buffer[..copy].CopyTo(sectors.AsSpan(skip));disk.AuthorizePartition(partition.Start,partition.Count);disk.Write(lba,sectors);
position+=copy;buffer=buffer[copy..];
}
}
}