/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: LongDirectoryEntry.sg // // Note: // // Based on pages 27-34 of "Microsoft Extensible Firmware // Initiative FAT32 File System Specification", Version 1.03, // December 6, 2000, Microsoft Corporation. using Microsoft.Singularity.Channels; using System.Runtime.InteropServices; using System; using Microsoft.Singularity.Io; namespace Microsoft.Singularity.Services.Fat.Fs { [StructLayout(LayoutKind.Sequential, Pack = 1)] internal pointerfree struct LongDirectoryEntry { internal const uint Length = 32; internal const int CharactersPerEntry = 13; internal const int MaxLongNameLength = 255; internal const int MaxShortNameAttempts = 1000000; private const byte LastLongEntryMask = 0x40; private const byte AttributeLongName = DirectoryEntry.AttributeLongName; // Long name terminator constants private const char TerminateFirst = Char.MinValue; private const char TerminateOther = Char.MaxValue; // Short name constants internal const int MaxBaseLength = DirectoryEntry.ShortNameMaxBaseName; internal const int MaxExtensionLength = DirectoryEntry.ShortNameMaxExtension; private byte ordinal; // 0 private char name00; // 1..2 private char name01; // 3..4 private char name02; // 5..6 private char name03; // 7..8 private char name04; // 9..10 internal byte Attribute; // 11 internal byte Type; // 12 private byte checksum; // 13 private char name05; // 14..15 private char name06; // 16..17 private char name07; // 18..19 private char name08; // 20..21 private char name09; // 22..23 private char name10; // 24..25 private char zero; // 26..27 private char name11; // 28..29 private char name12; // 30..31 internal bool IsLastEntry { get { assert this.IsValid; return (ordinal & LastLongEntryMask) == LastLongEntryMask; } } internal bool IsValid { get { return ((Attribute & DirectoryEntry.AttributeLongNameMask) == DirectoryEntry.AttributeLongName); } } internal int GetNumberOfLongEntries() { // This is only valid on last entry which has the ordinal bit set. assert ((int)ordinal & (int)LastLongEntryMask) != 0; return (int)ordinal & ~((int)LastLongEntryMask); } internal int GetPathLength() { // This is only valid on last entry which has the ordinal bit set. assert ((int)ordinal & (int)LastLongEntryMask) != 0; return (ComponentNumber - 1) * CharactersPerEntry + GetPathComponentLength(); } internal int ComponentNumber { get { return (int)ordinal & ~((int)LastLongEntryMask); } } internal byte Checksum { get { return checksum; } } internal int GetPathComponentLength() { if (name00 == TerminateFirst) return 0; if (name01 == TerminateFirst) return 1; if (name02 == TerminateFirst) return 2; if (name03 == TerminateFirst) return 3; if (name04 == TerminateFirst) return 4; if (name05 == TerminateFirst) return 5; if (name06 == TerminateFirst) return 6; if (name07 == TerminateFirst) return 7; if (name08 == TerminateFirst) return 8; if (name09 == TerminateFirst) return 9; if (name10 == TerminateFirst) return 10; if (name11 == TerminateFirst) return 11; if (name12 == TerminateFirst) return 12; return 13; } internal bool NameComponentMatches(char[]! in ExHeap name) { if (this.IsLastEntry) { return MatchNameLast(name); } return MatchName(name); } private bool MatchName(char[]! in ExHeap name) { // Component numbers are 1-indexed. int nOffset = (this.ComponentNumber - 1) * CharactersPerEntry; return Char.ToUpper(name00) == Char.ToUpper(name[nOffset + 0]) && Char.ToUpper(name01) == Char.ToUpper(name[nOffset + 1]) && Char.ToUpper(name02) == Char.ToUpper(name[nOffset + 2]) && Char.ToUpper(name03) == Char.ToUpper(name[nOffset + 3]) && Char.ToUpper(name04) == Char.ToUpper(name[nOffset + 4]) && Char.ToUpper(name05) == Char.ToUpper(name[nOffset + 5]) && Char.ToUpper(name06) == Char.ToUpper(name[nOffset + 6]) && Char.ToUpper(name07) == Char.ToUpper(name[nOffset + 7]) && Char.ToUpper(name08) == Char.ToUpper(name[nOffset + 8]) && Char.ToUpper(name09) == Char.ToUpper(name[nOffset + 9]) && Char.ToUpper(name10) == Char.ToUpper(name[nOffset + 10]) && Char.ToUpper(name11) == Char.ToUpper(name[nOffset + 11]) && Char.ToUpper(name12) == Char.ToUpper(name[nOffset + 12]); } private bool MatchNameLast(char[]! in ExHeap name) { int nOffset = (this.ComponentNumber - 1) * CharactersPerEntry; int todo = GetPathComponentLength(); assert name.Length == nOffset + todo; switch (todo) { case 13: if (Char.ToUpper(name12) != Char.ToUpper(name[nOffset + 12])) { return false; } goto case 12; case 12: if (Char.ToUpper(name11) != Char.ToUpper(name[nOffset + 11])) { return false; } goto case 11; case 11: if (Char.ToUpper(name10) != Char.ToUpper(name[nOffset + 10])) { return false; } goto case 10; case 10: if (Char.ToUpper(name09) != Char.ToUpper(name[nOffset + 9])) { return false; } goto case 9; case 9: if (Char.ToUpper(name08) != Char.ToUpper(name[nOffset + 8])) { return false; } goto case 8; case 8: if (Char.ToUpper(name07) != Char.ToUpper(name[nOffset + 7])) { return false; } goto case 7; case 7: if (Char.ToUpper(name06) != Char.ToUpper(name[nOffset + 6])) { return false; } goto case 6; case 6: if (Char.ToUpper(name05) != Char.ToUpper(name[nOffset + 5])) { return false; } goto case 5; case 5: if (Char.ToUpper(name04) != Char.ToUpper(name[nOffset + 4])) { return false; } goto case 4; case 4: if (Char.ToUpper(name03) != Char.ToUpper(name[nOffset + 3])) { return false; } goto case 3; case 3: if (Char.ToUpper(name02) != Char.ToUpper(name[nOffset + 2])) { return false; } goto case 2; case 2: if (Char.ToUpper(name01) != Char.ToUpper(name[nOffset + 1])) { return false; } goto case 1; case 1: if (Char.ToUpper(name00) != Char.ToUpper(name[nOffset + 0])) { return false; } break; } return true; } /// Copies characters from LongDirectoryEntry /// into character array at specified offset. internal void GetPathComponent(char[]! in ExHeap longName, int offset) { switch ( GetPathComponentLength() ) { case 13: longName[offset + 12] = name12; goto case 12; case 12: longName[offset + 11] = name11; goto case 11; case 11: longName[offset + 10] = name10; goto case 10; case 10: longName[offset + 9] = name09; goto case 9; case 9: longName[offset + 8] = name08; goto case 8; case 8: longName[offset + 7] = name07; goto case 7; case 7: longName[offset + 6] = name06; goto case 6; case 6: longName[offset + 5] = name05; goto case 5; case 5: longName[offset + 4] = name04; goto case 4; case 4: longName[offset + 3] = name03; goto case 3; case 3: longName[offset + 2] = name02; goto case 2; case 2: longName[offset + 1] = name01; goto case 1; case 1: longName[offset + 0] = name00; break; case 0: break; } } internal void GetPathComponent(char[]! in ExHeap longName) { int offset = (ComponentNumber - 1) * CharactersPerEntry; GetPathComponent(longName, offset); } private void SetCharacters(char[]! in ExHeap longName, int offset, int length) requires length > 0 && length <= CharactersPerEntry; { switch (length) { case 13: name12 = longName[offset + 12]; goto case 12; case 12: name11 = longName[offset + 11]; goto case 11; case 11: name10 = longName[offset + 10]; goto case 10; case 10: name09 = longName[offset + 9]; goto case 9; case 9: name08 = longName[offset + 8]; goto case 8; case 8: name07 = longName[offset + 7]; goto case 7; case 7: name06 = longName[offset + 6]; goto case 6; case 6: name05 = longName[offset + 5]; goto case 5; case 5: name04 = longName[offset + 4]; goto case 4; case 4: name03 = longName[offset + 3]; goto case 3; case 3: name02 = longName[offset + 2]; goto case 2; case 2: name01 = longName[offset + 1]; goto case 1; case 1: name00 = longName[offset + 0]; break; } } private void PadCharacters(int length) requires length > 0 && length < CharactersPerEntry; { char v = TerminateFirst; switch (length) { // case 13: unlucky for some and not allowed for padding. case 12: name01 = v; v = TerminateOther; goto case 11; case 11: name02 = v; v = TerminateOther; goto case 10; case 10: name03 = v; v = TerminateOther; goto case 9; case 9: name04 = v; v = TerminateOther; goto case 8; case 8: name05 = v; v = TerminateOther; goto case 7; case 7: name06 = v; v = TerminateOther; goto case 6; case 6: name07 = v; v = TerminateOther; goto case 5; case 5: name08 = v; v = TerminateOther; goto case 4; case 4: name09 = v; v = TerminateOther; goto case 3; case 3: name10 = v; v = TerminateOther; goto case 2; case 2: name11 = v; v = TerminateOther; goto case 1; case 1: name12 = v; v = TerminateOther; break; } } internal void SetNameComponent(int ordinal, int maxOrdinal, char[]! in ExHeap longName, byte shortNameChecksum) requires ordinal >= 1 && ordinal <= maxOrdinal; requires maxOrdinal >= 1; { this.Attribute = DirectoryEntry.AttributeLongName; this.Type = 0; this.checksum = shortNameChecksum; this.zero = Char.MinValue; int offset = (ordinal - 1) * CharactersPerEntry; int length = longName.Length - offset; if (ordinal == maxOrdinal) { ordinal |= (int)LastLongEntryMask; } this.ordinal = (byte)ordinal; if (length >= CharactersPerEntry) { SetCharacters(longName, offset, CharactersPerEntry); } else { assert length > 0; SetCharacters(longName, offset, length); PadCharacters(CharactersPerEntry - length); } } internal static byte ComputeChecksum(char[]! in ExHeap shortName) requires shortName.Length == MaxBaseLength + MaxExtensionLength; { byte sum = 0; for (int i = 0; i < MaxBaseLength + MaxExtensionLength; i++) { int tmp = ((((int)sum) & 1) << 7) + (((int)sum) >> 1); tmp += (int)shortName[i]; sum = unchecked((byte)tmp); } return sum; } private static void GetLongNameBounds(char[]! in ExHeap longName, out int start, out int length) { // Leading spaces are ignored. start = 0; while (start < longName.Length) { if (longName[start] != ' ') { break; } start++; } // Trailing spaces and periods are ignored. int end = longName.Length - 1; while (end >= 0) { if (longName[end] != ' ' && longName[end] != '.') { break; } end--; } length = end - start + 1; } private static int GetExtensionStart(char[]! in ExHeap name, int start, int length) { int extension = length - 1; while (extension >= start) { if (name[extension] == '.') { return extension + 1; } extension--; } return -1; } internal static bool ValidLongNameCharacter(char c) { return (c == '.' || c == '+' || c == ',' || c == ';' || c == '=' || c == '[' || c == ']' || DirectoryEntry.ValidShortNameCharacter(c)); } [ Microsoft.Contracts.Pure ] internal static bool ValidName(char[]! in ExHeap longName) { int start, length; GetLongNameBounds(longName, out start, out length); if (length <= 0 || length > MaxLongNameLength) { return false; } for (int i = 0; i < longName.Length; i++) { if (!ValidLongNameCharacter(longName[i])) { return false; } } return true; } static bool MapLongToShort(char input, out char output) { char c = Char.ToUpper(input); if (DirectoryEntry.ValidShortNameCharacter(c)) { output = c; return false; } else { output = '_'; return true; } } static int GetAbsoluteOffsetOfFinalPeriod(char[]! in ExHeap longName, int start, int length) { int offset = start + length - 2; while (offset > start) { if (longName[offset] == '.') { return offset; } offset--; } return Int32.MaxValue; } static bool CopyLongToShort(char[]! in ExHeap src, int srcStart, int srcLength, char[]! in ExHeap dst, int dstStart, int dstLength, out int dstUsed) { assert (srcStart >= 0 && srcStart <= src.Length); assert (srcLength >= 0 && srcStart + srcLength <= src.Length); assert (dstStart >= 0 && dstStart < dst.Length); assert (dstLength >= 0 && dstStart + dstLength <= dst.Length); bool lossy = false; // Skip leading spaces and periods while ((src[srcStart] == '.' || src[srcStart] == ' ') && srcLength != 0) { srcStart++; srcLength--; } dstUsed = 0; for (int i = 0; i < srcLength && dstUsed < dstLength; i++) { if (src[srcStart + i] == ' ') { continue; // Skip embedded spaces } lossy |= MapLongToShort(src[srcStart + i], out dst[dstStart + dstUsed]); dstUsed++; } return lossy; } static void WriteNumericTail(char[]! in ExHeap shortName, int baseNameLength, int attempt) { int tailWidth = (int)Math.Log10((double)attempt) + 2; // "~NNNN" int index = Math.Min(baseNameLength + tailWidth, DirectoryEntry.ShortNameMaxBaseName) - 1; while (attempt > 0) { shortName[index] = (char)('0' + (attempt % 10)); attempt /= 10; index --; } shortName[index] = '~'; } static internal void WriteShortNameEntry(char[]! in ExHeap longName, int attempt, char[]! in ExHeap shortName) requires longName.Length > 0; requires shortName.Length == DirectoryEntry.ShortNameEntryLength; requires attempt >= 1 && attempt < MaxShortNameAttempts; { assert (ValidName(longName)); for (int i = 0; i < shortName.Length; i++) { shortName[i] = ' '; } int longNameStart; int longNameLength; GetLongNameBounds(longName, out longNameStart, out longNameLength); int extensionStart = GetExtensionStart(longName, longNameStart, longNameLength); int baseUsed = 0; bool lossy; if (extensionStart > longNameStart) { lossy = CopyLongToShort( longName, longNameStart, extensionStart - longNameStart - 1, shortName, 0, DirectoryEntry.ShortNameMaxBaseName, out baseUsed ); int extUsed; lossy |= CopyLongToShort( longName, extensionStart, longNameStart + longNameLength - extensionStart, shortName, DirectoryEntry.ShortNameMaxBaseName, DirectoryEntry.ShortNameMaxExtension, out extUsed ); } else { lossy = CopyLongToShort( longName, longNameStart, longNameLength, shortName, 0, DirectoryEntry.ShortNameMaxBaseName, out baseUsed ); } WriteNumericTail(shortName, baseUsed, attempt); } private const int HiBitMask = 1 << (DHashtable.MaxKeyBits - 1); private static int HOP(int v) { // left rotation of DHashtable.MaxKeyBits wide unsigned integer return (v << 1) | ((v & HiBitMask) >> (DHashtable.MaxKeyBits - 1)); } internal int GetDirHashCode(int sum, int length) requires length > 0; requires length <= LongDirectoryEntry.CharactersPerEntry; { switch (length) { case 13: sum = HOP(sum) + Char.ToUpper(name12); sum &= DHashtable.MaxKeyValue; goto case 12; case 12: sum = HOP(sum) + Char.ToUpper(name11); sum &= DHashtable.MaxKeyValue; goto case 11; case 11: sum = HOP(sum) + Char.ToUpper(name10); sum &= DHashtable.MaxKeyValue; goto case 10; case 10: sum = HOP(sum) + Char.ToUpper(name09); sum &= DHashtable.MaxKeyValue; goto case 9; case 9: sum = HOP(sum) + Char.ToUpper(name08); sum &= DHashtable.MaxKeyValue; goto case 8; case 8: sum = HOP(sum) + Char.ToUpper(name07); sum &= DHashtable.MaxKeyValue; goto case 7; case 7: sum = HOP(sum) + Char.ToUpper(name06); sum &= DHashtable.MaxKeyValue; goto case 6; case 6: sum = HOP(sum) + Char.ToUpper(name05); sum &= DHashtable.MaxKeyValue; goto case 5; case 5: sum = HOP(sum) + Char.ToUpper(name04); sum &= DHashtable.MaxKeyValue; goto case 4; case 4: sum = HOP(sum) + Char.ToUpper(name03); sum &= DHashtable.MaxKeyValue; goto case 3; case 3: sum = HOP(sum) + Char.ToUpper(name02); sum &= DHashtable.MaxKeyValue; goto case 2; case 2: sum = HOP(sum) + Char.ToUpper(name01); sum &= DHashtable.MaxKeyValue; goto case 1; case 1: sum = HOP(sum) + Char.ToUpper(name00); sum &= DHashtable.MaxKeyValue; goto case 0; case 0: break; } return sum; } internal static int GetDirHashCode(char []! in ExHeap longName) { int start; int length; GetLongNameBounds(longName, out start, out length); int sum = 0; for (int i = start + length - 1; i >= start; i--) { sum = HOP(sum) + Char.ToUpper(longName[i]); sum &= DHashtable.MaxKeyValue; } return sum; } } }