Add FileSystemAttribute functionality

This commit is contained in:
Alex Barney 2023-10-05 00:22:41 -07:00
parent 809cecd1aa
commit 934f81da67
22 changed files with 509 additions and 17 deletions

View file

@ -0,0 +1,34 @@
#pragma warning disable CS0169, CS0649, IDE0051 // Field is never used, Field is never assigned to, Remove unused private members
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common.FixedArrays;
public struct Array100<T>
{
public const int Length = 100;
private Array64<T> _0;
private Array36<T> _64;
[UnscopedRef] public ref T this[int i] => ref Items[i];
[UnscopedRef]
public Span<T> Items
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length);
}
[UnscopedRef]
public readonly ReadOnlySpan<T> ItemsRo
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(in Array100<T> value) => value.ItemsRo;
}

View file

@ -31,6 +31,7 @@ internal class FileSystemAccessor : IDisposable
private LinkedList<FileAccessor> _openFiles;
private LinkedList<DirectoryAccessor> _openDirectories;
private SdkMutexType _openListLock;
private SdkMutexType _getFsAttributeLock;
private UniqueRef<ICommonMountNameGenerator> _mountNameGenerator;
private UniqueRef<ISaveDataAttributeGetter> _saveDataAttributeGetter;
private bool _isAccessLogEnabled;
@ -39,6 +40,7 @@ internal class FileSystemAccessor : IDisposable
private bool _isPathCacheAttached;
private IMultiCommitTarget _multiCommitTarget;
private PathFlags _pathFlags;
private Optional<FileSystemAttribute> _fsAttribute;
private IStorage _storageForPurgeFileDataCache;
internal HorizonClient Hos { get; }
@ -53,6 +55,7 @@ internal class FileSystemAccessor : IDisposable
_openFiles = new LinkedList<FileAccessor>();
_openDirectories = new LinkedList<DirectoryAccessor>();
_openListLock = new SdkMutexType();
_getFsAttributeLock = new SdkMutexType();
_mountNameGenerator = new UniqueRef<ICommonMountNameGenerator>(ref mountNameGenerator);
_saveDataAttributeGetter = new UniqueRef<ISaveDataAttributeGetter>(ref saveAttributeGetter);
_multiCommitTarget = multiCommitTarget;
@ -457,6 +460,25 @@ internal class FileSystemAccessor : IDisposable
return Result.Success;
}
public Result GetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _getFsAttributeLock);
if (_fsAttribute.HasValue)
{
outAttribute = _fsAttribute.Value;
}
else
{
Result res = _fileSystem.Get.GetFileSystemAttribute(out outAttribute);
if (res.IsFailure()) return res.Miss();
_fsAttribute.Set(in outAttribute);
}
return Result.Success;
}
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, U8Span path)
{
using var pathNormalized = new Path();

View file

@ -1,5 +1,7 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.FsSystem;
namespace LibHac.Fs.Fsa;
@ -297,6 +299,16 @@ public abstract class IFileSystem : IDisposable
return DoQueryEntry(outBuffer, inBuffer, queryId, path);
}
/// <summary>
/// Gets attributes of the <see cref="IFileSystem"/> including info about the maximum path length sizes it supports.
/// </summary>
/// <param name="outAttribute">If the operation returns successfully, the file system attributes.</param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
public Result GetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
return DoGetFileSystemAttribute(out outAttribute);
}
protected abstract Result DoCreateFile(in Path path, long size, CreateFileOptions option);
protected abstract Result DoDeleteFile(in Path path);
protected abstract Result DoCreateDirectory(in Path path);
@ -320,8 +332,7 @@ public abstract class IFileSystem : IDisposable
}
protected abstract Result DoOpenFile(ref UniqueRef<IFile> outFile, in Path path, OpenMode mode);
protected abstract Result DoOpenDirectory(ref UniqueRef<IDirectory> outDirectory, in Path path,
OpenDirectoryMode mode);
protected abstract Result DoOpenDirectory(ref UniqueRef<IDirectory> outDirectory, in Path path, OpenDirectoryMode mode);
protected abstract Result DoCommit();
protected virtual Result DoCommitProvisionally(long counter) => ResultFs.NotImplemented.Log();
@ -336,6 +347,12 @@ public abstract class IFileSystem : IDisposable
protected virtual Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
in Path path) => ResultFs.NotImplemented.Log();
protected virtual Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
UnsafeHelpers.SkipParamInit(out outAttribute);
return ResultFs.NotImplemented.Log();
}
}
/// <summary>
@ -373,4 +390,37 @@ public enum QueryId
UpdateMac = 1,
IsSignedSystemPartition = 2,
QueryUnpreparedFileInformation = 3
}
[StructLayout(LayoutKind.Sequential)]
public struct FileSystemAttribute
{
public bool DirectoryNameLengthMaxHasValue;
public bool FileNameLengthMaxHasValue;
public bool DirectoryPathLengthMaxHasValue;
public bool FilePathLengthMaxHasValue;
public bool Utf16CreateDirectoryPathLengthMaxHasValue;
public bool Utf16DeleteDirectoryPathLengthMaxHasValue;
public bool Utf16RenameSourceDirectoryPathLengthMaxHasValue;
public bool Utf16RenameDestinationDirectoryPathLengthMaxHasValue;
public bool Utf16OpenDirectoryPathLengthMaxHasValue;
public bool Utf16DirectoryNameLengthMaxHasValue;
public bool Utf16FileNameLengthMaxHasValue;
public bool Utf16DirectoryPathLengthMaxHasValue;
public bool Utf16FilePathLengthMaxHasValue;
public Array27<byte> Reserved1;
public int DirectoryNameLengthMax;
public int FileNameLengthMax;
public int DirectoryPathLengthMax;
public int FilePathLengthMax;
public int Utf16CreateDirectoryPathLengthMax;
public int Utf16DeleteDirectoryPathLengthMax;
public int Utf16RenameSourceDirectoryPathLengthMax;
public int Utf16RenameDestinationDirectoryPathLengthMax;
public int Utf16OpenDirectoryPathLengthMax;
public int Utf16DirectoryNameLengthMax;
public int Utf16FileNameLengthMax;
public int Utf16DirectoryPathLengthMax;
public int Utf16FilePathLengthMax;
public Array100<byte> Reserved2;
}

View file

@ -76,6 +76,9 @@ public static class Registrar
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) =>
_fileSystem.Get.GetFreeSpaceSize(out freeSpace, in path);
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) =>
_fileSystem.Get.GetFileSystemAttribute(out outAttribute);
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) =>
_fileSystem.Get.GetTotalSpaceSize(out totalSpace, in path);
@ -183,7 +186,7 @@ public static class Registrar
Assert.SdkAssert(storageForPurgeFileDataCache is not null);
using (var unmountHookFileSystem =
new UniqueRef<UnmountHookFileSystem>(new UnmountHookFileSystem(ref fileSystem, ref unmountHook)))
new UniqueRef<UnmountHookFileSystem>(new UnmountHookFileSystem(ref fileSystem, ref unmountHook)))
{
fileSystem.Set(ref unmountHookFileSystem.Ref);
}

View file

@ -1,6 +1,8 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
@ -9,7 +11,7 @@ namespace LibHac.Fs.Fsa;
/// <summary>
/// Contains functions meant for internal use for interacting with mounted file systems.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0 (FS 13.1.0)</remarks>
/// <remarks>Based on nnSdk 16.2.0</remarks>
public static class UserFileSystemPrivate
{
public static Result CreateFile(this FileSystemClient fs, U8Span path, long size, CreateFileOptions option)
@ -26,7 +28,7 @@ public static class UserFileSystemPrivate
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogPath).Append(path).Append((byte)'"');
sb.Append(LogPath).Append(path).Append(LogQuote);
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLogUnlessResultSuccess(res, start, end, null, new U8Span(logBuffer));
@ -45,7 +47,7 @@ public static class UserFileSystemPrivate
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize).AppendFormat(size);
sb.Append(LogPath).Append(path).Append(LogQuote).Append(LogSize).AppendFormat(size);
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLog(res, start, end, null, new U8Span(logBuffer));
@ -97,4 +99,134 @@ public static class UserFileSystemPrivate
fs.Impl.AbortIfNeeded(res);
return res;
}
private static void SetOrChangeMin(ref int minValue, ref bool hasMinValue, int value, bool hasValue)
{
if (!hasValue)
return;
if (!hasMinValue)
{
minValue = value;
hasMinValue = true;
return;
}
if (minValue > value)
{
minValue = value;
}
}
public static Result GetPathLengthMax(this FileSystemClient fs, out long outLength, U8Span path)
{
UnsafeHelpers.SkipParamInit(out outLength);
Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
res = fileSystem.GetFileSystemAttribute(out FileSystemAttribute attribute);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
int retValue = 0;
bool hasRetValue = false;
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.DirectoryPathLengthMax, attribute.DirectoryPathLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.FilePathLengthMax, attribute.FileNameLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, FspPath.MaxLength, true);
Assert.SdkAssert(hasRetValue);
outLength = retValue;
return Result.Success;
}
public static Result GetEntryNameLengthMax(this FileSystemClient fs, out long outLength, U8Span path)
{
UnsafeHelpers.SkipParamInit(out outLength);
Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
res = fileSystem.GetFileSystemAttribute(out FileSystemAttribute attribute);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
int retValue = 0;
bool hasRetValue = false;
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.DirectoryNameLengthMax, attribute.DirectoryNameLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.FileNameLengthMax, attribute.FileNameLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, FspPath.MaxLength, true);
Assert.SdkAssert(hasRetValue);
outLength = retValue;
return Result.Success;
}
public static Result GetUtf16PathLengthMax(this FileSystemClient fs, out bool outHasValue, out long outLength, U8Span path)
{
UnsafeHelpers.SkipParamInit(out outHasValue, out outLength);
Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
res = fileSystem.GetFileSystemAttribute(out FileSystemAttribute attribute);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
int retValue = 0;
bool hasRetValue = false;
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16CreateDirectoryPathLengthMax, attribute.Utf16CreateDirectoryPathLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16DeleteDirectoryPathLengthMax, attribute.Utf16DeleteDirectoryPathLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16RenameSourceDirectoryPathLengthMax, attribute.Utf16RenameSourceDirectoryPathLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16RenameDestinationDirectoryPathLengthMax, attribute.Utf16RenameDestinationDirectoryPathLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16OpenDirectoryPathLengthMax, attribute.Utf16OpenDirectoryPathLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16FilePathLengthMax, attribute.Utf16FilePathLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16DirectoryPathLengthMax, attribute.Utf16DirectoryPathLengthMaxHasValue);
outHasValue = hasRetValue;
outLength = retValue;
return Result.Success;
}
public static Result GetUtf16EntryNameLengthMax(this FileSystemClient fs, out bool outHasValue, out long outLength, U8Span path)
{
UnsafeHelpers.SkipParamInit(out outHasValue, out outLength);
Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
res = fileSystem.GetFileSystemAttribute(out FileSystemAttribute attribute);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
int retValue = 0;
bool hasRetValue = false;
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16FileNameLengthMax, attribute.Utf16FileNameLengthMaxHasValue);
SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16DirectoryNameLengthMax, attribute.Utf16DirectoryNameLengthMaxHasValue);
outHasValue = hasRetValue;
outLength = retValue;
return Result.Success;
}
public static Result GetFileSystemAttributeForDebug(this FileSystemClient fs, out FileSystemAttribute outAttribute, U8Span path)
{
UnsafeHelpers.SkipParamInit(out outAttribute);
Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
res = fileSystem.GetFileSystemAttribute(out outAttribute);
fs.Impl.AbortIfNeeded(res);
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
}

View file

@ -10,7 +10,7 @@ namespace LibHac.Fs;
/// Wraps an <see cref="IFile"/> and only allows read operations on it.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0 (FS 13.1.0)</remarks>
internal class ReadOnlyFile : IFile
file class ReadOnlyFile : IFile
{
private UniqueRef<IFile> _baseFile;
@ -156,4 +156,9 @@ public class ReadOnlyFileSystem : IFileSystem
Unsafe.SkipInit(out totalSpace);
return ResultFs.UnsupportedGetTotalSpaceSizeForReadOnlyFileSystem.Log();
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
return _baseFileSystem.Get.GetFileSystemAttribute(out outAttribute);
}
}

View file

@ -293,6 +293,11 @@ internal class FileSystemServiceObjectAdapter : IFileSystem, IMultiCommitTarget
return _baseFs.Get.GetFileTimeStampRaw(out timeStamp, in sfPath);
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
return _baseFs.Get.GetFileSystemAttribute(out outAttribute);
}
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
in Path path)
{

View file

@ -592,6 +592,17 @@ public class FileSystemInterfaceAdapter : IFileSystemSf
return Result.Success;
}
public Result GetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
UnsafeHelpers.SkipParamInit(out outAttribute);
Result res = _baseFileSystem.Get.GetFileSystemAttribute(out FileSystemAttribute attribute);
if (res.IsFailure()) return res.Miss();
outAttribute = attribute;
return Result.Success;
}
public Result GetImpl(ref SharedRef<IFileSystem> fileSystem)
{
fileSystem.SetByCopy(in _baseFileSystem);

View file

@ -114,4 +114,9 @@ public class SaveDataFileSystemCacheRegister : IFileSystem
{
return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path);
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
return _baseFileSystem.Get.GetFileSystemAttribute(out outAttribute);
}
}

View file

@ -1,6 +1,7 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Sf;
using IFileSf = LibHac.FsSrv.Sf.IFile;
using IDirectorySf = LibHac.FsSrv.Sf.IDirectory;
@ -26,4 +27,5 @@ public interface IFileSystem : IDisposable
Result CleanDirectoryRecursively(in Path path);
Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path);
Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in Path path);
Result GetFileSystemAttribute(out FileSystemAttribute outAttribute);
}

View file

@ -502,8 +502,7 @@ public class ConcatenationFileSystem : IFileSystem
/// <param name="baseFileSystem">The base <see cref="IAttributeFileSystem"/> for the
/// new <see cref="ConcatenationFileSystem"/>.</param>
public ConcatenationFileSystem(ref UniqueRef<IAttributeFileSystem> baseFileSystem) : this(ref baseFileSystem,
DefaultInternalFileSize)
{ }
DefaultInternalFileSize) { }
/// <summary>
/// Initializes a new <see cref="ConcatenationFileSystem"/>.
@ -582,7 +581,7 @@ public class ConcatenationFileSystem : IFileSystem
Result res = internalFilePath.Initialize(in path);
if (res.IsFailure()) return res.Miss();
for (int i = 0; ; i++)
for (int i = 0;; i++)
{
res = AppendInternalFilePath(ref internalFilePath.Ref(), i);
if (res.IsFailure()) return res.Miss();
@ -958,7 +957,7 @@ public class ConcatenationFileSystem : IFileSystem
long sizeTotal = 0;
for (int i = 0; ; i++)
for (int i = 0;; i++)
{
res = AppendInternalFilePath(ref internalFilePath.Ref(), i);
if (res.IsFailure()) return res.Miss();
@ -1011,4 +1010,11 @@ public class ConcatenationFileSystem : IFileSystem
return _baseFileSystem.Get.CommitProvisionally(counter).Ret();
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
using var scopedLock = new ScopedLock<SdkMutexType>(ref _mutex);
return _baseFileSystem.Get.GetFileSystemAttribute(out outAttribute).Ret();
}
}

View file

@ -682,6 +682,18 @@ public class DirectorySaveDataFileSystem : ISaveDataFileSystem
return Result.Success;
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
const int baseDirectoryNameLength = 2;
Result res = _baseFs.GetFileSystemAttribute(out outAttribute);
if (res.IsFailure()) return res.Miss();
Utility.SubtractAllPathLengthMax(ref outAttribute, baseDirectoryNameLength);
Utility.SubtractAllUtf16CountMax(ref outAttribute, baseDirectoryNameLength);
return Result.Success;
}
public override bool IsSaveDataFileSystemCacheEnabled()
{
return false;

View file

@ -139,6 +139,9 @@ public class ForwardingFileSystem : IFileSystem
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) =>
BaseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, in path);
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) =>
BaseFileSystem.Get.GetFileSystemAttribute(out outAttribute);
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
in Path path) => BaseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, in path);
}

View file

@ -207,4 +207,9 @@ public abstract class IResultConvertFileSystem<T> : ISaveDataFileSystem where T
{
return ConvertResult(_baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path)).Ret();
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
return ConvertResult(_baseFileSystem.Get.GetFileSystemAttribute(out outAttribute)).Ret();
}
}

View file

@ -523,6 +523,44 @@ public class LocalFileSystem : IAttributeFileSystem
return Result.Success;
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
const int winMaxPathComponentLength = 255;
const int winMaxPathLength = 259;
const int winMaxDirectoryPathLength = 247;
outAttribute = default;
outAttribute.Utf16DirectoryNameLengthMax = winMaxPathComponentLength;
outAttribute.Utf16DirectoryNameLengthMaxHasValue = true;
outAttribute.Utf16FileNameLengthMax = winMaxPathComponentLength;
outAttribute.Utf16FileNameLengthMaxHasValue = true;
outAttribute.Utf16DirectoryPathLengthMax = winMaxPathLength;
outAttribute.Utf16DirectoryPathLengthMaxHasValue = true;
outAttribute.Utf16FilePathLengthMax = winMaxPathLength;
outAttribute.Utf16FilePathLengthMaxHasValue = true;
outAttribute.Utf16CreateDirectoryPathLengthMax = winMaxDirectoryPathLength;
outAttribute.Utf16CreateDirectoryPathLengthMaxHasValue = true;
outAttribute.Utf16DeleteDirectoryPathLengthMax = winMaxPathLength;
outAttribute.Utf16DeleteDirectoryPathLengthMaxHasValue = true;
outAttribute.Utf16RenameSourceDirectoryPathLengthMax = winMaxDirectoryPathLength;
outAttribute.Utf16RenameSourceDirectoryPathLengthMaxHasValue = true;
outAttribute.Utf16RenameDestinationDirectoryPathLengthMax = winMaxDirectoryPathLength;
outAttribute.Utf16RenameDestinationDirectoryPathLengthMaxHasValue = true;
outAttribute.Utf16OpenDirectoryPathLengthMax = winMaxPathLength;
outAttribute.Utf16OpenDirectoryPathLengthMaxHasValue = true;
int rootPathCount = _rootPath.GetLength();
Result res = Utility.CountUtf16CharacterForUtf8String(out ulong rootPathUtf16Count, _rootPath.GetString());
if (res.IsFailure()) return res.Miss();
Utility.SubtractAllPathLengthMax(ref outAttribute, rootPathCount);
Utility.SubtractAllUtf16CountMax(ref outAttribute, (int)rootPathUtf16Count);
return Result.Success;
}
protected override Result DoCommit()
{
return Result.Success;

View file

@ -38,10 +38,7 @@ internal struct ScopedStorageLayoutTypeSetter : IDisposable
// Todo: Implement
}
public void Dispose()
{
}
public void Dispose() { }
}
/// <summary>
@ -377,6 +374,12 @@ internal class StorageLayoutTypeSetFileSystem : IFileSystem
return _baseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, path);
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag);
return _baseFileSystem.Get.GetFileSystemAttribute(out outAttribute);
}
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
in Path path)
{

View file

@ -255,4 +255,20 @@ public class SubdirectoryFileSystem : IFileSystem
{
return _baseFileSystem.Rollback();
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
int rootPathCount = _rootPath.GetLength();
Result res = _baseFileSystem.GetFileSystemAttribute(out outAttribute);
if (res.IsFailure()) return res.Miss();
res = Utility.CountUtf16CharacterForUtf8String(out ulong rootPathUtf16Count, _rootPath.GetString());
if (res.IsFailure()) return res.Miss();
Utility.SubtractAllPathLengthMax(ref outAttribute, rootPathCount);
Utility.SubtractAllUtf16CountMax(ref outAttribute, (int)rootPathUtf16Count);
return Result.Success;
}
}

View file

@ -1,10 +1,12 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Sf;
using LibHac.Os;
using LibHac.Sf;
using LibHac.Util;
using IDirectory = LibHac.Fs.Fsa.IDirectory;
using IFile = LibHac.Fs.Fsa.IFile;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
@ -208,7 +210,7 @@ internal static class Utility
}
public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem,
in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span<byte> workBuffer)
in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span<byte> workBuffer)
{
static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure)
{
@ -251,7 +253,7 @@ internal static class Utility
}
public static Result CopyDirectoryRecursively(IFileSystem fileSystem, in Path destinationPath,
in Path sourcePath, ref DirectoryEntry dirEntry, Span<byte> workBuffer)
in Path sourcePath, ref DirectoryEntry dirEntry, Span<byte> workBuffer)
{
var closure = new FsIterationTaskClosure();
closure.Buffer = workBuffer;
@ -438,4 +440,73 @@ internal static class Utility
outUniqueLock.Set(ref uniqueLock.Ref);
return Result.Success;
}
private static void SubtractIfHasValue(ref int inOutValue, int count, bool hasValue)
{
if (hasValue)
{
inOutValue -= count;
Assert.SdkAssert(inOutValue >= 0);
inOutValue = Math.Max(inOutValue, 0);
}
}
private static ulong GetCodePointByteLength(byte firstUtf8CodeUnit)
{
if ((firstUtf8CodeUnit & 0x80) == 0)
return 1;
if ((firstUtf8CodeUnit & 0xE0) == 0xC0)
return 2;
if ((firstUtf8CodeUnit & 0xF0) == 0xE0)
return 3;
if ((firstUtf8CodeUnit & 0xF8) == 0xF0)
return 4;
return 0;
}
public static void SubtractAllPathLengthMax(ref FileSystemAttribute attribute, int count)
{
SubtractIfHasValue(ref attribute.DirectoryPathLengthMax, count, attribute.DirectoryPathLengthMaxHasValue);
SubtractIfHasValue(ref attribute.FilePathLengthMax, count, attribute.FilePathLengthMaxHasValue);
}
public static Result CountUtf16CharacterForUtf8String(out ulong outCount, ReadOnlySpan<byte> utf8String)
{
UnsafeHelpers.SkipParamInit(out outCount);
Span<byte> buffer = stackalloc byte[4];
ReadOnlySpan<byte> curString = utf8String;
ulong utf16CodeUnitTotalCount = 0;
while (curString.Length > 0 && curString[0] != 0)
{
int utf16CodeUnitCount = GetCodePointByteLength(curString[0]) >= 4 ? 2 : 1;
buffer.Clear();
if (CharacterEncoding.PickOutCharacterFromUtf8String(buffer, ref curString) != CharacterEncodingResult.Success)
{
return ResultFs.InvalidPathFormat.Log();
}
utf16CodeUnitTotalCount += (ulong)utf16CodeUnitCount;
}
outCount = utf16CodeUnitTotalCount;
return Result.Success;
}
public static void SubtractAllUtf16CountMax(ref FileSystemAttribute attribute, int count)
{
SubtractIfHasValue(ref attribute.Utf16DirectoryPathLengthMax, count, attribute.Utf16DirectoryPathLengthMaxHasValue);
SubtractIfHasValue(ref attribute.Utf16FilePathLengthMax, count, attribute.Utf16FilePathLengthMaxHasValue);
SubtractIfHasValue(ref attribute.Utf16CreateDirectoryPathLengthMax, count, attribute.Utf16CreateDirectoryPathLengthMaxHasValue);
SubtractIfHasValue(ref attribute.Utf16DeleteDirectoryPathLengthMax, count, attribute.Utf16DeleteDirectoryPathLengthMaxHasValue);
SubtractIfHasValue(ref attribute.Utf16RenameSourceDirectoryPathLengthMax, count, attribute.Utf16RenameSourceDirectoryPathLengthMaxHasValue);
SubtractIfHasValue(ref attribute.Utf16RenameDestinationDirectoryPathLengthMax, count, attribute.Utf16RenameDestinationDirectoryPathLengthMaxHasValue);
SubtractIfHasValue(ref attribute.Utf16OpenDirectoryPathLengthMax, count, attribute.Utf16OpenDirectoryPathLengthMaxHasValue);
}
}

View file

@ -117,6 +117,17 @@ public class RomFsFileSystem : IFileSystem
return ResultFs.UnsupportedGetTotalSpaceSizeForRomFsFileSystem.Log();
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
outAttribute = default;
outAttribute.DirectoryNameLengthMax = PathTool.EntryNameLengthMax;
outAttribute.DirectoryNameLengthMaxHasValue = true;
outAttribute.FileNameLengthMax = PathTool.EntryNameLengthMax;
outAttribute.FileNameLengthMaxHasValue = true;
return Result.Success;
}
internal static Result ConvertRomFsDriverPrivateResult(Result result)
{
if (result.IsSuccess())

View file

@ -247,6 +247,13 @@ public class SaveDataFileSystem : IFileSystem
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
Result result = SaveDataFileSystemCore.GetFileSystemAttribute(out outAttribute);
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
}
protected override Result DoCommit()
{
Result result = Commit(KeySet);

View file

@ -229,6 +229,19 @@ public class SaveDataFileSystemCore : IFileSystem
return Result.Success;
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
const int maxSaveNameLength = 0x40;
outAttribute = default;
outAttribute.DirectoryNameLengthMax = maxSaveNameLength;
outAttribute.DirectoryNameLengthMaxHasValue = true;
outAttribute.FileNameLengthMax = maxSaveNameLength;
outAttribute.FileNameLengthMaxHasValue = true;
return Result.Success;
}
protected override Result DoCommit()
{
return Result.Success;

View file

@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using Xunit;
using static LibHac.Tests.Common.Layout;
@ -258,6 +259,43 @@ public class TypeLayoutTests
Assert.Equal(0xC, GetOffset(in s, in s.NumReadWriteErrorCorrections));
}
[Fact]
public static void FileSystemAttribute_Layout()
{
var s = new FileSystemAttribute();
Assert.Equal(0xC0, Unsafe.SizeOf<FileSystemAttribute>());
Assert.Equal(0x00, GetOffset(in s, in s.DirectoryNameLengthMaxHasValue));
Assert.Equal(0x01, GetOffset(in s, in s.FileNameLengthMaxHasValue));
Assert.Equal(0x02, GetOffset(in s, in s.DirectoryPathLengthMaxHasValue));
Assert.Equal(0x03, GetOffset(in s, in s.FilePathLengthMaxHasValue));
Assert.Equal(0x04, GetOffset(in s, in s.Utf16CreateDirectoryPathLengthMaxHasValue));
Assert.Equal(0x05, GetOffset(in s, in s.Utf16DeleteDirectoryPathLengthMaxHasValue));
Assert.Equal(0x06, GetOffset(in s, in s.Utf16RenameSourceDirectoryPathLengthMaxHasValue));
Assert.Equal(0x07, GetOffset(in s, in s.Utf16RenameDestinationDirectoryPathLengthMaxHasValue));
Assert.Equal(0x08, GetOffset(in s, in s.Utf16OpenDirectoryPathLengthMaxHasValue));
Assert.Equal(0x09, GetOffset(in s, in s.Utf16DirectoryNameLengthMaxHasValue));
Assert.Equal(0x0A, GetOffset(in s, in s.Utf16FileNameLengthMaxHasValue));
Assert.Equal(0x0B, GetOffset(in s, in s.Utf16DirectoryPathLengthMaxHasValue));
Assert.Equal(0x0C, GetOffset(in s, in s.Utf16FilePathLengthMaxHasValue));
Assert.Equal(0x0D, GetOffset(in s, in s.Reserved1));
Assert.Equal(0x28, GetOffset(in s, in s.DirectoryNameLengthMax));
Assert.Equal(0x2C, GetOffset(in s, in s.FileNameLengthMax));
Assert.Equal(0x30, GetOffset(in s, in s.DirectoryPathLengthMax));
Assert.Equal(0x34, GetOffset(in s, in s.FilePathLengthMax));
Assert.Equal(0x38, GetOffset(in s, in s.Utf16CreateDirectoryPathLengthMax));
Assert.Equal(0x3C, GetOffset(in s, in s.Utf16DeleteDirectoryPathLengthMax));
Assert.Equal(0x40, GetOffset(in s, in s.Utf16RenameSourceDirectoryPathLengthMax));
Assert.Equal(0x44, GetOffset(in s, in s.Utf16RenameDestinationDirectoryPathLengthMax));
Assert.Equal(0x48, GetOffset(in s, in s.Utf16OpenDirectoryPathLengthMax));
Assert.Equal(0x4C, GetOffset(in s, in s.Utf16DirectoryNameLengthMax));
Assert.Equal(0x50, GetOffset(in s, in s.Utf16FileNameLengthMax));
Assert.Equal(0x54, GetOffset(in s, in s.Utf16DirectoryPathLengthMax));
Assert.Equal(0x58, GetOffset(in s, in s.Utf16FilePathLengthMax));
Assert.Equal(0x5C, GetOffset(in s, in s.Reserved2));
}
[Fact]
public static void FileTimeStamp_Layout()
{