Skip to content

Commit

Permalink
Add support for writing long file name to FAT
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Sep 11, 2024
1 parent 608c43d commit b4181f8
Show file tree
Hide file tree
Showing 13 changed files with 1,561 additions and 593 deletions.
4 changes: 3 additions & 1 deletion DiscUtils.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,11 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=VFAT/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
173 changes: 108 additions & 65 deletions Library/DiscUtils.Fat/Directory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,23 @@ internal class Directory : IDisposable
private readonly long _parentId;
private long _endOfEntries;

private Dictionary<long, DirectoryEntry> _entries;
private List<long> _freeEntries;
private readonly Dictionary<long, DirectoryEntry> _entries;
private readonly Dictionary<string, long> _shortFileNameToEntry;
private readonly Dictionary<string, long> _fullFileNameToEntry;

private FreeDirectoryEntryTable _freeDirectoryEntryTable;

private DirectoryEntry _parentEntry;
private long _parentEntryLocation;

private DirectoryEntry _selfEntry;
private long _selfEntryLocation;

/// <summary>
/// Delegate to check if a short name exists in the directory used by <see cref="FatFileName.FromName"/>
/// </summary>
internal readonly Func<string, bool> CheckIfShortNameExists;

/// <summary>
/// Initializes a new instance of the Directory class. Use this constructor to represent non-root directories.
/// </summary>
Expand All @@ -51,8 +60,13 @@ internal class Directory : IDisposable
internal Directory(Directory parent, long parentId)
{
FileSystem = parent.FileSystem;
_entries = new();
_freeDirectoryEntryTable = new();
_shortFileNameToEntry = new(StringComparer.OrdinalIgnoreCase);
_fullFileNameToEntry = new(StringComparer.OrdinalIgnoreCase);
_parent = parent;
_parentId = parentId;
CheckIfShortNameExists = CheckIfShortNameExistsImpl;

var dirEntry = ParentsChildEntry;
_dirStream = new ClusterStream(FileSystem, FileAccess.ReadWrite, dirEntry.FirstCluster, uint.MaxValue);
Expand All @@ -68,7 +82,12 @@ internal Directory(Directory parent, long parentId)
internal Directory(FatFileSystem fileSystem, Stream dirStream)
{
FileSystem = fileSystem;
_entries = new();
_freeDirectoryEntryTable = new();
_shortFileNameToEntry = new(StringComparer.OrdinalIgnoreCase);
_fullFileNameToEntry = new(StringComparer.OrdinalIgnoreCase);
_dirStream = dirStream;
CheckIfShortNameExists = CheckIfShortNameExistsImpl;

LoadEntries();
}
Expand Down Expand Up @@ -112,7 +131,7 @@ public DirectoryEntry GetEntry(long id)
return id < 0 ? null : _entries[id];
}

public Directory GetChildDirectory(FileName name)
public Directory GetChildDirectory(FatFileName name)
{
var id = FindEntry(name);
if (id < 0)
Expand All @@ -128,7 +147,7 @@ public Directory GetChildDirectory(FileName name)
return FileSystem.GetDirectory(this, id);
}

internal Directory CreateChildDirectory(FileName name)
internal Directory CreateChildDirectory(FatFileName name)
{
var id = FindEntry(name);
if (id >= 0)
Expand Down Expand Up @@ -172,24 +191,18 @@ internal Directory CreateChildDirectory(FileName name)
}
}

internal void AttachChildDirectory(FileName name, Directory newChild)
internal void AttachChildDirectory(in FatFileName name, Directory newChild)
{
var id = FindEntry(name);
if (id >= 0)
{
throw new IOException("Directory entry already exists");
}

var newEntry = new DirectoryEntry(newChild.ParentsChildEntry)
{
Name = name
};
var newEntry = new DirectoryEntry(newChild.ParentsChildEntry, name);
AddEntry(newEntry);

var newParentEntry = new DirectoryEntry(SelfEntry)
{
Name = FileName.ParentEntryName
};
var newParentEntry = new DirectoryEntry(SelfEntry, FatFileName.ParentEntryName);
newChild.ParentEntry = newParentEntry;
}

Expand All @@ -207,21 +220,23 @@ internal long FindVolumeId()
return -1;
}

internal long FindEntry(FileName name)
internal long FindEntry(in FatFileName name)
{
foreach (var id in _entries.Keys)
#if NET6_0_OR_GREATER
return _fullFileNameToEntry.GetValueOrDefault(name.FullName, -1);
#else
if (_fullFileNameToEntry.TryGetValue(name.FullName, out var id))
{
var focus = _entries[id];
if (focus.Name == name && (focus.Attributes & FatAttributes.VolumeId) == 0)
{
return id;
}
return id;
}

return -1;
#endif
}

internal FatFileStream OpenFile(FileName name, FileMode mode, FileAccess fileAccess)
private bool CheckIfShortNameExistsImpl(string shortName) => _shortFileNameToEntry.ContainsKey(shortName);

internal FatFileStream OpenFile(FatFileName name, FileMode mode, FileAccess fileAccess)
{
if (mode is FileMode.Append or FileMode.Truncate)
{
Expand All @@ -238,8 +253,7 @@ internal FatFileStream OpenFile(FileName name, FileMode mode, FileAccess fileAcc

if (mode == FileMode.Open && !exists)
{
throw new FileNotFoundException("File not found",
name.GetDisplayName(FileSystem.FatOptions.FileNameEncoding));
throw new FileNotFoundException("File not found", name);
}

if ((mode == FileMode.Open || mode == FileMode.OpenOrCreate || mode == FileMode.Create) && exists)
Expand Down Expand Up @@ -278,25 +292,22 @@ internal FatFileStream OpenFile(FileName name, FileMode mode, FileAccess fileAcc
internal long AddEntry(DirectoryEntry newEntry)
{
// Unlink an entry from the free list (or add to the end of the existing directory)
long pos;
if (_freeEntries.Count > 0)
{
pos = _freeEntries[0];
_freeEntries.RemoveAt(0);
}
else
var entryCount = newEntry.EntryCount;
var pos = _freeDirectoryEntryTable.Allocate(newEntry.EntryCount);

if (pos < 0)
{
pos = _endOfEntries;
_endOfEntries += 32;
_endOfEntries += entryCount * DirectoryEntry.SizeOf;
}

// Put the new entry into it's slot
_dirStream.Position = pos;
newEntry.WriteTo(_dirStream);
newEntry.WriteTo(_dirStream, FileSystem.FatOptions.FileNameEncodingTable);

// Update internal structures to reflect new entry (as if read from disk)
_entries.Add(pos, newEntry);

AddEntryRaw(pos, newEntry);
HandleAccessed(forWrite: true);

return pos;
Expand All @@ -313,21 +324,24 @@ internal void DeleteEntry(long id, bool releaseContents)
{
var entry = _entries[id];

var copy = new DirectoryEntry(entry)
{
Name = entry.Name.Deleted()
};
var entryCount = entry.EntryCount;

_dirStream.Position = id;
copy.WriteTo(_dirStream);
DirectoryEntry.WriteDeletedEntry(_dirStream, entry.EntryCount);

if (releaseContents)
{
FileSystem.Fat.FreeChain(entry.FirstCluster);
}

_entries.Remove(id);
_freeEntries.Add(id);

_freeDirectoryEntryTable.AddFreeRange(id, entryCount);

// Remove from the short and full name lookup tables
_shortFileNameToEntry.Remove(entry.Name.ShortName);
_fullFileNameToEntry.Remove(entry.Name.FullName);

HandleAccessed(true);
}
finally
Expand All @@ -344,39 +358,47 @@ internal void UpdateEntry(long id, DirectoryEntry entry)
}

_dirStream.Position = id;
entry.WriteTo(_dirStream);
entry.WriteTo(_dirStream, FileSystem.FatOptions.FileNameEncodingTable);
_entries[id] = entry;
}

private void LoadEntries()
private void AddEntryRaw(long pos, DirectoryEntry entry)
{
_entries = [];
_freeEntries = [];
_entries.Add(pos, entry);
// Update the short and full name lookup tables
_shortFileNameToEntry.Add(entry.Name.ShortName, pos);
_fullFileNameToEntry.Add(entry.Name.FullName, pos);
}

private void LoadEntries()
{
_selfEntryLocation = -1;
_parentEntryLocation = -1;
long beginFreePosition = -1;
long endFreePosition = -1;

while (_dirStream.Position < _dirStream.Length)
{
var streamPos = _dirStream.Position;
var entry = new DirectoryEntry(FileSystem.FatOptions, _dirStream, FileSystem.FatVariant);
var streamPos = _dirStream.Position - 32;

if (entry.Attributes ==
(FatAttributes.ReadOnly | FatAttributes.Hidden | FatAttributes.System | FatAttributes.VolumeId))

if ((entry.Attributes & FatAttributes.LongFileNameMask) == FatAttributes.LongFileName)
{
// Long File Name entry
// Orphaned Long File Name entry
AddFreeEntry(streamPos);
}
else if (entry.Name.IsDeleted())
{
// E5 = Free Entry
_freeEntries.Add(streamPos);
// LFN Orphane entries (that are not part of a valid entry) are also marked as free
AddFreeEntry(streamPos);
}
else if (entry.Name == FileName.SelfEntryName)
else if (entry.Name == FatFileName.SelfEntryName)
{
_selfEntry = entry;
_selfEntryLocation = streamPos;
}
else if (entry.Name == FileName.ParentEntryName)
else if (entry.Name == FatFileName.ParentEntryName)
{
_parentEntry = entry;
_parentEntryLocation = streamPos;
Expand All @@ -389,11 +411,36 @@ private void LoadEntries()
}
else
{
_entries.Add(streamPos, entry);
AddEntryRaw(streamPos, entry);
}
}

// Record any pending free entry
AddFreeEntry(-1);

void AddFreeEntry(long pos)
{
if (beginFreePosition >= 0)
{
// If a free entry comes after the previous one, and is contiguous, extend the count
if (endFreePosition + DirectoryEntry.SizeOf == pos)
{
endFreePosition = pos;
return;
}

// Record any pending range
AddFreeRange(beginFreePosition, (int)((endFreePosition - beginFreePosition) / DirectoryEntry.SizeOf));
}

beginFreePosition = pos;
endFreePosition = pos + DirectoryEntry.SizeOf;
}
}

private void AddFreeRange(long position, int count)
=> _freeDirectoryEntryTable.AddFreeRange(position, count);

private void HandleAccessed(bool forWrite)
{
if (FileSystem.CanWrite && _parent != null)
Expand Down Expand Up @@ -428,20 +475,16 @@ private void PopulateNewChildDirectory(DirectoryEntry newEntry)
using var stream = new ClusterStream(FileSystem, FileAccess.Write, newEntry.FirstCluster,
uint.MaxValue);
// First is the self-referencing entry...
var selfEntry = new DirectoryEntry(newEntry)
{
Name = FileName.SelfEntryName
};
selfEntry.WriteTo(stream);
var selfEntry = new DirectoryEntry(newEntry, FatFileName.SelfEntryName);
selfEntry.WriteTo(stream, FileSystem.FatOptions.FileNameEncodingTable);

// Second is a clone of our self entry (i.e. parent) - though dates are odd...
var parentEntry = new DirectoryEntry(SelfEntry)
var parentEntry = new DirectoryEntry(SelfEntry, FatFileName.ParentEntryName)
{
Name = FileName.ParentEntryName,
CreationTime = newEntry.CreationTime,
LastWriteTime = newEntry.LastWriteTime
};
parentEntry.WriteTo(stream);
parentEntry.WriteTo(stream, FileSystem.FatOptions.FileNameEncodingTable);
}

private void Dispose(bool disposing)
Expand All @@ -460,7 +503,7 @@ internal DirectoryEntry ParentsChildEntry
{
if (_parent == null)
{
return new DirectoryEntry(FileSystem.FatOptions, FileName.ParentEntryName, FatAttributes.Directory,
return new DirectoryEntry(FileSystem.FatOptions, FatFileName.ParentEntryName, FatAttributes.Directory,
FileSystem.FatVariant);
}

Expand All @@ -477,7 +520,7 @@ internal DirectoryEntry SelfEntry
if (_parent == null)
{
// If we're the root directory, simulate the parent entry with a dummy record
return new DirectoryEntry(FileSystem.FatOptions, FileName.Null, FatAttributes.Directory,
return new DirectoryEntry(FileSystem.FatOptions, FatFileName.Null, FatAttributes.Directory,
FileSystem.FatVariant);
}

Expand All @@ -489,7 +532,7 @@ internal DirectoryEntry SelfEntry
if (_selfEntryLocation >= 0)
{
_dirStream.Position = _selfEntryLocation;
value.WriteTo(_dirStream);
value.WriteTo(_dirStream, FileSystem.FatOptions.FileNameEncodingTable);
_selfEntry = value;
}
}
Expand All @@ -507,7 +550,7 @@ internal DirectoryEntry ParentEntry
}

_dirStream.Position = _parentEntryLocation;
value.WriteTo(_dirStream);
value.WriteTo(_dirStream, FileSystem.FatOptions.FileNameEncodingTable);
_parentEntry = value;
}
}
Expand Down
Loading

0 comments on commit b4181f8

Please sign in to comment.