646 lines
25 KiB
Plaintext
646 lines
25 KiB
Plaintext
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
/// <summary> Copies characters from LongDirectoryEntry
|
|
/// into character array at specified offset. </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|