Ensure more FS classes are updated for 13.1.0

- Add some PooledBuffer global functions

- FsSrv.SaveDataInfoFilter
- FsSrv.SaveDataInfoFilterReader
- FsSrv.StatusReportService
- FsSrv.StatusReportServiceImpl
- FsSrv.TimeService
- FsSrv.TimeServiceImpl
- FsSystem.DefaultAsynchronousAccessSplitter
- FsSystem.IAsynchronousAccessSplitter
- FsSystem.StorageFile
This commit is contained in:
Alex Barney 2022-01-26 20:06:59 -07:00
parent 2c2fed445f
commit 32ffda1d3f
9 changed files with 333 additions and 197 deletions

View file

@ -1,5 +1,6 @@
using LibHac.FsSrv.Impl;
using LibHac.FsSrv.Storage;
using LibHac.FsSystem;
namespace LibHac.FsSrv;
@ -34,6 +35,7 @@ internal struct FileSystemServerGlobals
public SaveDataSharedFileStorageGlobals SaveDataSharedFileStorage;
public MultiCommitManagerGlobals MultiCommitManager;
public LocationResolverSetGlobals LocationResolverSet;
public PooledBufferGlobals PooledBuffer;
public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer)
{
@ -43,6 +45,7 @@ internal struct FileSystemServerGlobals
SaveDataSharedFileStorage.Initialize(fsServer);
MultiCommitManager.Initialize();
LocationResolverSet.Initialize();
PooledBuffer.Initialize();
}
}

View file

@ -150,8 +150,8 @@ public static class FileSystemServerInitializer
var saveFsService = new SaveDataFileSystemServiceImpl(in saveFsServiceConfig);
var statusReportServiceConfig = new StatusReportServiceImpl.Configuration();
statusReportServiceConfig.NcaFsServiceImpl = ncaFsService;
statusReportServiceConfig.SaveFsServiceImpl = saveFsService;
statusReportServiceConfig.NcaFileSystemServiceImpl = ncaFsService;
statusReportServiceConfig.SaveDataFileSystemServiceImpl = saveFsService;
statusReportServiceConfig.BufferManagerMemoryReport = null;
statusReportServiceConfig.ExpHeapMemoryReport = null;
statusReportServiceConfig.BufferPoolMemoryReport = null;

View file

@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
@ -8,6 +9,108 @@ using LibHac.Util;
namespace LibHac.FsSrv;
/// <summary>
/// Contains filter parameters for <see cref="SaveDataInfo"/> and can check
/// to see if a <see cref="SaveDataInfo"/> matches those parameters.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
internal struct SaveDataInfoFilter
{
private Optional<SaveDataSpaceId> _spaceId;
private Optional<ProgramId> _programId;
private Optional<SaveDataType> _saveDataType;
private Optional<UserId> _userId;
private Optional<ulong> _saveDataId;
private Optional<ushort> _index;
private int _rank;
public SaveDataInfoFilter(in SaveDataInfoFilter filter)
{
this = filter;
}
public SaveDataInfoFilter(SaveDataSpaceId spaceId, in SaveDataFilter filter)
{
// Start out with no optional values
this = default;
_spaceId = new Optional<SaveDataSpaceId>(spaceId);
_rank = (int)filter.Rank;
if (filter.FilterByProgramId)
{
_programId = new Optional<ProgramId>(filter.Attribute.ProgramId);
}
if (filter.FilterBySaveDataType)
{
_saveDataType = new Optional<SaveDataType>(filter.Attribute.Type);
}
if (filter.FilterByUserId)
{
_userId = new Optional<UserId>(in filter.Attribute.UserId);
}
if (filter.FilterBySaveDataId)
{
_saveDataId = new Optional<ulong>(filter.Attribute.StaticSaveDataId);
}
if (filter.FilterByIndex)
{
_index = new Optional<ushort>(filter.Attribute.Index);
}
}
public SaveDataInfoFilter(Optional<SaveDataSpaceId> spaceId, Optional<ProgramId> programId,
Optional<SaveDataType> saveDataType, Optional<UserId> userId, Optional<ulong> saveDataId,
Optional<ushort> index, int rank)
{
_spaceId = spaceId;
_programId = programId;
_saveDataType = saveDataType;
_userId = userId;
_saveDataId = saveDataId;
_index = index;
_rank = rank;
}
public bool Includes(in SaveDataInfo saveInfo)
{
if (_spaceId.HasValue && saveInfo.SpaceId != _spaceId.Value)
return false;
if (_programId.HasValue && saveInfo.ProgramId != _programId.Value)
return false;
if (_saveDataType.HasValue && saveInfo.Type != _saveDataType.Value)
return false;
if (_userId.HasValue && saveInfo.UserId != _userId.Value)
return false;
if (_saveDataId.HasValue && saveInfo.SaveDataId != _saveDataId.Value)
return false;
if (_index.HasValue && saveInfo.Index != _index.Value)
return false;
var filterRank = (SaveDataRank)(_rank & 1);
// When filtering by secondary rank, match on both primary and secondary ranks
if (filterRank == SaveDataRank.Primary && saveInfo.Rank == SaveDataRank.Secondary)
return false;
return true;
}
}
/// <summary>
/// Wraps a <see cref="SaveDataInfoReaderImpl"/> and only allows <see cref="SaveDataInfo"/>
/// that match a provided <see cref="SaveDataInfoFilter"/> to be returned.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
internal class SaveDataInfoFilterReader : SaveDataInfoReaderImpl
{
private SharedRef<SaveDataInfoReaderImpl> _reader;
@ -19,146 +122,35 @@ internal class SaveDataInfoFilterReader : SaveDataInfoReaderImpl
_infoFilter = infoFilter;
}
public void Dispose()
{
_reader.Destroy();
}
[SkipLocalsInit]
public Result Read(out long readCount, OutBuffer saveDataInfoBuffer)
{
UnsafeHelpers.SkipParamInit(out readCount);
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer.Buffer);
SaveDataInfo tempInfo = default;
Span<byte> tempInfoBytes = SpanHelpers.AsByteSpan(ref tempInfo);
SaveDataInfoReaderImpl reader = _reader.Get;
int count = 0;
while (count < outInfo.Length)
{
Result rc = reader.Read(out long baseReadCount, new OutBuffer(tempInfoBytes));
Unsafe.SkipInit(out SaveDataInfo info);
Result rc = _reader.Get.Read(out long baseReadCount, OutBuffer.FromStruct(ref info));
if (rc.IsFailure()) return rc;
if (baseReadCount == 0) break;
if (_infoFilter.Includes(in tempInfo))
if (_infoFilter.Includes(in info))
{
outInfo[count] = tempInfo;
outInfo[count] = info;
count++;
}
}
readCount = count;
return Result.Success;
}
public void Dispose()
{
_reader.Destroy();
}
}
internal struct SaveDataInfoFilter
{
public Optional<SaveDataSpaceId> SpaceId;
public Optional<ProgramId> ProgramId;
public Optional<SaveDataType> SaveDataType;
public Optional<UserId> UserId;
public Optional<ulong> SaveDataId;
public Optional<ushort> Index;
public int Rank;
public SaveDataInfoFilter(in SaveDataInfoFilter filter)
{
this = filter;
}
public SaveDataInfoFilter(SaveDataSpaceId spaceId, in SaveDataFilter filter)
{
// Start out with no optional values
this = new SaveDataInfoFilter();
SpaceId = new Optional<SaveDataSpaceId>(spaceId);
Rank = (int)filter.Rank;
if (filter.FilterByProgramId)
{
ProgramId = new Optional<ProgramId>(in filter.Attribute.ProgramId);
}
if (filter.FilterBySaveDataType)
{
SaveDataType = new Optional<SaveDataType>(in filter.Attribute.Type);
}
if (filter.FilterByUserId)
{
UserId = new Optional<UserId>(in filter.Attribute.UserId);
}
if (filter.FilterBySaveDataId)
{
SaveDataId = new Optional<ulong>(in filter.Attribute.StaticSaveDataId);
}
if (filter.FilterByIndex)
{
Index = new Optional<ushort>(in filter.Attribute.Index);
}
}
public SaveDataInfoFilter(Optional<SaveDataSpaceId> spaceId, Optional<ProgramId> programId,
Optional<SaveDataType> saveDataType, Optional<UserId> userId, Optional<ulong> saveDataId,
Optional<ushort> index, int rank)
{
SpaceId = spaceId;
ProgramId = programId;
SaveDataType = saveDataType;
UserId = userId;
SaveDataId = saveDataId;
Index = index;
Rank = rank;
}
public bool Includes(in SaveDataInfo saveInfo)
{
if (SpaceId.HasValue && saveInfo.SpaceId != SpaceId.Value)
{
return false;
}
if (ProgramId.HasValue && saveInfo.ProgramId != ProgramId.Value)
{
return false;
}
if (SaveDataType.HasValue && saveInfo.Type != SaveDataType.Value)
{
return false;
}
if (UserId.HasValue && saveInfo.UserId != UserId.Value)
{
return false;
}
if (SaveDataId.HasValue && saveInfo.SaveDataId != SaveDataId.Value)
{
return false;
}
if (Index.HasValue && saveInfo.Index != Index.Value)
{
return false;
}
var filterRank = (SaveDataRank)(Rank & 1);
// When filtering by secondary rank, match on both primary and secondary ranks
if (filterRank == SaveDataRank.Primary && saveInfo.Rank == SaveDataRank.Secondary)
{
return false;
}
return true;
}
}
}

View file

@ -1,9 +1,16 @@
using LibHac.Diag;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Os;
namespace LibHac.FsSrv;
/// <summary>
/// Handles status-report-related calls for <see cref="FileSystemProxyImpl"/>.
/// </summary>
/// <remarks><para>This struct handles forwarding calls to the <see cref="StatusReportServiceImpl"/> object.
/// No permissions are needed to call any of this struct's functions.</para>
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para></remarks>
public readonly struct StatusReportService
{
private readonly StatusReportServiceImpl _serviceImpl;
@ -30,6 +37,10 @@ public readonly struct StatusReportService
}
}
/// <summary>
/// Manages getting and resetting various status reports and statistics about parts of the FS service.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public class StatusReportServiceImpl
{
private Configuration _config;
@ -38,13 +49,13 @@ public class StatusReportServiceImpl
public StatusReportServiceImpl(in Configuration configuration)
{
_config = configuration;
_mutex.Initialize();
_mutex = new SdkMutexType();
}
public struct Configuration
{
public NcaFileSystemServiceImpl NcaFsServiceImpl;
public SaveDataFileSystemServiceImpl SaveFsServiceImpl;
public NcaFileSystemServiceImpl NcaFileSystemServiceImpl;
public SaveDataFileSystemServiceImpl SaveDataFileSystemServiceImpl;
// Missing: FatFileSystemCreator (Not an IFatFileSystemCreator)
public MemoryReport BufferManagerMemoryReport;
public MemoryReport ExpHeapMemoryReport;
@ -60,15 +71,19 @@ public class StatusReportServiceImpl
public Result GetAndClearFileSystemProxyErrorInfo(out FileSystemProxyErrorInfo errorInfo)
{
errorInfo = new FileSystemProxyErrorInfo();
errorInfo = default;
_config.NcaFsServiceImpl.GetAndClearRomFsErrorInfo(out errorInfo.RemountForDataCorruptionCount,
Assert.SdkRequiresNotNull(_config.NcaFileSystemServiceImpl);
_config.NcaFileSystemServiceImpl.GetAndClearRomFsErrorInfo(out errorInfo.RemountForDataCorruptionCount,
out errorInfo.UnrecoverableDataCorruptionByRemountCount,
out errorInfo.RecoveredByInvalidateCacheCount);
// Missing: GetFatInfo
Result rc = _config.SaveFsServiceImpl.GetSaveDataIndexCount(out int count);
Assert.SdkRequiresNotNull(_config.SaveDataFileSystemServiceImpl);
Result rc = _config.SaveDataFileSystemServiceImpl.GetSaveDataIndexCount(out int count);
if (rc.IsFailure()) return rc;
errorInfo.SaveDataIndexCount = count;
@ -77,40 +92,40 @@ public class StatusReportServiceImpl
public Result GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo)
{
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
reportInfo = new MemoryReportInfo();
reportInfo = default;
// Missing: Get and clear pooled buffer stats
reportInfo.PooledBufferFreeSizePeak = 0;
reportInfo.PooledBufferRetriedCount = 0;
reportInfo.PooledBufferReduceAllocationCount = 0;
reportInfo.PooledBufferFreeSizePeak = _config.FsServer.GetPooledBufferFreeSizePeak();
reportInfo.PooledBufferRetriedCount = _config.FsServer.GetPooledBufferRetriedCount();
reportInfo.PooledBufferReduceAllocationCount = _config.FsServer.GetPooledBufferReduceAllocationCount();
reportInfo.PooledBufferFailedIdealAllocationCountOnAsyncAccess =
_config.FsServer.GetPooledBufferFailedIdealAllocationCountOnAsyncAccess();
MemoryReport report = _config.BufferManagerMemoryReport;
if (report != null)
_config.FsServer.ClearPooledBufferPeak();
if (_config.BufferManagerMemoryReport is not null)
{
reportInfo.BufferManagerFreeSizePeak = report.GetFreeSizePeak();
reportInfo.BufferManagerTotalAllocatableSizePeak = report.GetTotalAllocatableSizePeak();
reportInfo.BufferManagerRetriedCount = report.GetRetriedCount();
report.Clear();
reportInfo.BufferManagerFreeSizePeak = _config.BufferManagerMemoryReport.GetFreeSizePeak();
reportInfo.BufferManagerTotalAllocatableSizePeak = _config.BufferManagerMemoryReport.GetTotalAllocatableSizePeak();
reportInfo.BufferManagerRetriedCount = _config.BufferManagerMemoryReport.GetRetriedCount();
_config.BufferManagerMemoryReport.Clear();
}
report = _config.ExpHeapMemoryReport;
if (report != null)
if (_config.ExpHeapMemoryReport is not null)
{
reportInfo.ExpHeapFreeSizePeak = report.GetFreeSizePeak();
report.Clear();
reportInfo.ExpHeapFreeSizePeak = _config.ExpHeapMemoryReport.GetFreeSizePeak();
_config.ExpHeapMemoryReport.Clear();
}
report = _config.BufferPoolMemoryReport;
if (report != null)
if (_config.BufferPoolMemoryReport is not null)
{
reportInfo.BufferPoolFreeSizePeak = report.GetFreeSizePeak();
reportInfo.BufferPoolAllocateSizeMax = report.GetAllocateSizeMax();
report.Clear();
reportInfo.BufferPoolFreeSizePeak = _config.BufferPoolMemoryReport.GetFreeSizePeak();
reportInfo.BufferPoolAllocateSizeMax = _config.BufferPoolMemoryReport.GetAllocateSizeMax();
_config.BufferPoolMemoryReport.Clear();
}
if (_config.GetPatrolAllocateCounts != null)
if (_config.GetPatrolAllocateCounts is not null)
{
_config.GetPatrolAllocateCounts(out reportInfo.PatrolReadAllocateBufferSuccessCount,
out reportInfo.PatrolReadAllocateBufferFailureCount);

View file

@ -6,6 +6,12 @@ using LibHac.Os;
namespace LibHac.FsSrv;
/// <summary>
/// Handles time-related calls for <see cref="FileSystemProxyImpl"/>.
/// </summary>
/// <remarks><para>This struct handles checking a process' permissions before forwarding
/// a request to the <see cref="TimeServiceImpl"/> object.</para>
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para></remarks>
public readonly struct TimeService
{
private readonly TimeServiceImpl _serviceImpl;
@ -19,7 +25,8 @@ public readonly struct TimeService
public Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference)
{
Result rc = GetProgramInfo(out ProgramInfo programInfo);
using var programRegistry = new ProgramRegistryImpl(_serviceImpl.FsServer);
Result rc = programRegistry.GetProgramInfo(out ProgramInfo programInfo, _processId);
if (rc.IsFailure()) return rc;
if (!programInfo.AccessControl.CanCall(OperationType.SetCurrentPosixTime))
@ -28,32 +35,33 @@ public readonly struct TimeService
_serviceImpl.SetCurrentPosixTimeWithTimeDifference(currentTime, timeDifference);
return Result.Success;
}
private Result GetProgramInfo(out ProgramInfo programInfo)
{
return _serviceImpl.GetProgramInfo(out programInfo, _processId);
}
}
/// <summary>
/// Manages the current time used by the FS service.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public class TimeServiceImpl
{
private long _basePosixTime;
private int _timeDifference;
private int _timeDifferenceSeconds;
private SdkMutexType _mutex;
private FileSystemServer _fsServer;
// LibHac addition
internal FileSystemServer FsServer { get; }
public TimeServiceImpl(FileSystemServer fsServer)
{
_fsServer = fsServer;
_basePosixTime = 0;
_timeDifference = 0;
_mutex.Initialize();
_timeDifferenceSeconds = 0;
_mutex = new SdkMutexType();
FsServer = fsServer;
}
private long GetSystemSeconds()
{
OsState os = _fsServer.Hos.Os;
OsState os = FsServer.Hos.Os;
Tick tick = os.GetSystemTick();
TimeSpan timeSpan = os.ConvertToTimeSpan(tick);
@ -69,7 +77,7 @@ public class TimeServiceImpl
{
UnsafeHelpers.SkipParamInit(out currentTime, out timeDifference);
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
if (_basePosixTime == 0)
return ResultFs.NotInitialized.Log();
@ -81,7 +89,7 @@ public class TimeServiceImpl
if (!Unsafe.IsNullRef(ref timeDifference))
{
timeDifference = _timeDifference;
timeDifference = _timeDifferenceSeconds;
}
return Result.Success;
@ -89,15 +97,9 @@ public class TimeServiceImpl
public void SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference)
{
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
_basePosixTime = currentTime - GetSystemSeconds();
_timeDifference = timeDifference;
_timeDifferenceSeconds = timeDifference;
}
internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId)
{
var registry = new ProgramRegistryImpl(_fsServer);
return registry.GetProgramInfo(out programInfo, processId);
}
}
}

View file

@ -1,10 +1,19 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem;
/// <summary>
/// <para>Splits read and write requests on an <see cref="IFile"/> or <see cref="IStorage"/> into smaller chunks
/// so the request can be processed by multiple threads simultaneously.</para>
/// <para>This interface exists because of <see cref="CompressedStorage"/> where it will split requests into
/// chunks that start and end on the boundaries of the compressed blocks.</para>
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public interface IAsynchronousAccessSplitter : IDisposable
{
private static readonly DefaultAsynchronousAccessSplitter DefaultAccessSplitter = new();
@ -57,6 +66,11 @@ public interface IAsynchronousAccessSplitter : IDisposable
Result QueryAppropriateOffset(out long offsetAppropriate, long startOffset, long accessSize, long alignmentSize);
}
/// <summary>
/// The default <see cref="IAsynchronousAccessSplitter"/> that is used when an <see cref="IStorage"/>
/// or <see cref="IFile"/> doesn't need any special logic to split a request into multiple chunks.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public class DefaultAsynchronousAccessSplitter : IAsynchronousAccessSplitter
{
public void Dispose() { }

View file

@ -1,9 +1,64 @@
using System;
using System.Buffers;
using LibHac.Diag;
using LibHac.FsSrv;
using LibHac.Os;
namespace LibHac.FsSystem;
public static class PooledBufferGlobalMethods
{
public static long GetPooledBufferRetriedCount(this FileSystemServer fsSrv)
{
return fsSrv.Globals.PooledBuffer.CountRetried;
}
public static long GetPooledBufferReduceAllocationCount(this FileSystemServer fsSrv)
{
return fsSrv.Globals.PooledBuffer.CountReduceAllocation;
}
public static long GetPooledBufferFailedIdealAllocationCountOnAsyncAccess(this FileSystemServer fsSrv)
{
return fsSrv.Globals.PooledBuffer.CountFailedIdealAllocationOnAsyncAccess;
}
public static long GetPooledBufferFreeSizePeak(this FileSystemServer fsSrv)
{
ref PooledBufferGlobals g = ref fsSrv.Globals.PooledBuffer;
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref g.HeapMutex);
return g.SizeHeapFreePeak;
}
public static void ClearPooledBufferPeak(this FileSystemServer fsSrv)
{
ref PooledBufferGlobals g = ref fsSrv.Globals.PooledBuffer;
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref g.HeapMutex);
// Missing: Get SizeHeapFreePeak from the heap object
g.CountRetried = 0;
g.CountReduceAllocation = 0;
g.CountFailedIdealAllocationOnAsyncAccess = 0;
}
}
internal struct PooledBufferGlobals
{
public SdkMutexType HeapMutex;
public long SizeHeapFreePeak;
public Memory<byte> HeapBuffer;
public long CountRetried;
public long CountReduceAllocation;
public long CountFailedIdealAllocationOnAsyncAccess;
public void Initialize()
{
HeapMutex = new SdkMutexType();
}
}
// Implement the PooledBuffer interface using .NET ArrayPools
public struct PooledBuffer : IDisposable
{

View file

@ -1,59 +1,68 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSystem;
/// <summary>
/// Allows interacting with an <see cref="IStorage"/> via an <see cref="IFile"/> interface.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public class StorageFile : IFile
{
private IStorage BaseStorage { get; }
private OpenMode Mode { get; }
private IStorage _baseStorage;
private OpenMode _mode;
public StorageFile(IStorage baseStorage, OpenMode mode)
{
BaseStorage = baseStorage;
Mode = mode;
_baseStorage = baseStorage;
_mode = mode;
}
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination,
in ReadOption option)
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
{
UnsafeHelpers.SkipParamInit(out bytesRead);
Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode);
Assert.SdkRequiresNotNull(_baseStorage);
Result rc = DryRead(out long readSize, offset, destination.Length, in option, _mode);
if (rc.IsFailure()) return rc;
if (toRead == 0)
if (readSize == 0)
{
bytesRead = 0;
return Result.Success;
}
rc = BaseStorage.Read(offset, destination.Slice(0, (int)toRead));
rc = _baseStorage.Read(offset, destination.Slice(0, (int)readSize));
if (rc.IsFailure()) return rc;
bytesRead = toRead;
bytesRead = readSize;
return Result.Success;
}
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
{
Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode);
Assert.SdkRequiresNotNull(_baseStorage);
Result rc = DryWrite(out bool isAppendNeeded, offset, source.Length, in option, _mode);
if (rc.IsFailure()) return rc;
if (isResizeNeeded)
if (isAppendNeeded)
{
rc = DoSetSize(offset + source.Length);
if (rc.IsFailure()) return rc;
}
rc = BaseStorage.Write(offset, source);
rc = _baseStorage.Write(offset, source);
if (rc.IsFailure()) return rc;
if (option.HasFlushFlag())
{
return Flush();
rc = Flush();
if (rc.IsFailure()) return rc.Miss();
}
return Result.Success;
@ -61,28 +70,68 @@ public class StorageFile : IFile
protected override Result DoFlush()
{
if (!Mode.HasFlag(OpenMode.Write))
Assert.SdkRequiresNotNull(_baseStorage);
if (!_mode.HasFlag(OpenMode.Write))
return Result.Success;
return BaseStorage.Flush();
return _baseStorage.Flush();
}
protected override Result DoGetSize(out long size)
{
return BaseStorage.GetSize(out size);
Assert.SdkRequiresNotNull(_baseStorage);
return _baseStorage.GetSize(out size);
}
protected override Result DoSetSize(long size)
{
if (!Mode.HasFlag(OpenMode.Write))
return ResultFs.WriteUnpermitted.Log();
Assert.SdkRequiresNotNull(_baseStorage);
return BaseStorage.SetSize(size);
Result rc = DrySetSize(size, _mode);
if (rc.IsFailure()) return rc.Miss();
return _baseStorage.SetSize(size);
}
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> inBuffer)
{
return ResultFs.NotImplemented.Log();
Assert.SdkRequiresNotNull(_baseStorage);
switch (operationId)
{
case OperationId.InvalidateCache:
{
if (!_mode.HasFlag(OpenMode.Read))
return ResultFs.ReadUnpermitted.Log();
Result rc = _baseStorage.OperateRange(OperationId.InvalidateCache, offset, size);
if (rc.IsFailure()) return rc.Miss();
break;
}
case OperationId.QueryRange:
{
if (offset < 0)
return ResultFs.InvalidOffset.Log();
Result rc = GetSize(out long fileSize);
if (rc.IsFailure()) return rc.Miss();
long operableSize = Math.Max(0, fileSize - offset);
long operateSize = Math.Min(operableSize, size);
rc = _baseStorage.OperateRange(outBuffer, operationId, offset, operateSize, inBuffer);
if (rc.IsFailure()) return rc.Miss();
break;
}
default:
return ResultFs.UnsupportedOperateRangeForStorageFile.Log();
}
return Result.Success;
}
}
}

View file

@ -35,6 +35,12 @@ public struct Optional<T>
_hasValue = true;
}
public Optional(T value)
{
_value = value;
_hasValue = true;
}
public static implicit operator Optional<T>(in T value) => new Optional<T>(in value);
public void Set(T value)