/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: BlockIndex.sg // // Note: // The BlockIndex is used to provide fast block location within // files and directories. Its structure is fairly similar to the direct // and indirect blocks of a traditional inode. // // The maximum size of a FAT file is 4GB. The code support 3 levels // of indirection. The direct block fan out is 32 and the // indirect block fan out is 64. On the third indirection we can // address all of a 4GB file with 512 byte clusters: // // Addressable region = 32 * 64^3 * 512 == 4GB // // In practice, we should expect to see 4k cluster sizes and upwards. // using System; using System.Diagnostics; namespace Microsoft.Singularity.Services.Fat.Fs { internal sealed class BlockIndex { /////////////////////////////////////////////////////////////////////// // Internal class definitions internal abstract class BaseBlock { } internal sealed class DirectBlock : BaseBlock { internal const int Shift = 6; internal const int MaxItems = 1 << Shift; private int []! items; internal DirectBlock() { items = new int [MaxItems]; base(); } public int this[int index] { get { return items[index]; } set { items[index] = value; } } } internal sealed class IndirectBlock : BaseBlock { internal const int Shift = 7; internal const int MaxItems = 1 << Shift; private BaseBlock [] items; internal IndirectBlock() { items = new BaseBlock [MaxItems]; } public BaseBlock this[int index] { get { return items[index]; } set { Debug.Assert(items[index] == null); items[index] = value; } } } /////////////////////////////////////////////////////////////////////// // Constants private const int MaxIndirects = 3; /////////////////////////////////////////////////////////////////////// // Members int count; readonly int maxCount; DirectBlock direct; IndirectBlock [] indirects; /////////////////////////////////////////////////////////////////////// // Methods internal BlockIndex() { this.indirects = new IndirectBlock[MaxIndirects]; this.direct = new DirectBlock(); maxCount = DirectBlock.MaxItems; int delta = IndirectBlock.MaxItems * DirectBlock.MaxItems; for (int i = 0; i < MaxIndirects; i++) { maxCount += delta; delta *= IndirectBlock.MaxItems; } } static private int DirectOffset(int value) { return value & ((1 << DirectBlock.Shift) - 1); } static private int IndirectOffset(int maxDepth, int depth, int value) { value >>= DirectBlock.Shift; value >>= (IndirectBlock.Shift * (maxDepth - depth)); return value & ((1 << IndirectBlock.Shift) - 1); } /// /// Accessor providing number of blocks indexed. /// internal int Count { get { return count; } } /// /// Lookup the block id of the nth block in the index. /// /// true if block exists in index, /// false otherwise. /// internal bool Lookup(int nthBlock, out int blockId) { if (count <= nthBlock) { blockId = Int32.MinValue; return false; } // Check if block is in direct blocks if (nthBlock < DirectBlock.MaxItems) { blockId = direct[nthBlock]; return true; } nthBlock -= DirectBlock.MaxItems; // Must be an indirect block. Find which indirect block to // start from. int threshold = IndirectBlock.MaxItems * DirectBlock.MaxItems; int depth = 0; while (nthBlock >= threshold) { nthBlock -= threshold; threshold *= IndirectBlock.MaxItems; depth++; } // Walk from root indirect block down to block containing // appropriate direct block. IndirectBlock! ib = (!) indirects[depth]; for (int i = 0; i < depth; i++) { ib = (IndirectBlock!) ib[IndirectOffset(depth, i, nthBlock)]; } // Get entry in direct block and pray for forgiveness. DirectBlock! db = (DirectBlock)(!)ib[IndirectOffset(depth, depth, nthBlock)]; blockId = db[DirectOffset(nthBlock)]; return true; } private bool ScanDirectBlockForId(DirectBlock! db, int blockLength, int blockId, out int dbIndex) { for (dbIndex = 0; dbIndex < blockLength; dbIndex++) { if (db[dbIndex] == blockId) { return true; } } dbIndex = -1; return false; } private bool ScanIndirectBlockForId(int depth, IndirectBlock! idb, int done, int stepSize, int blockId, out DirectBlock db, out int dbIndex, out int absoluteIndex) { if (depth == 0) { int i = 0; while (i < IndirectBlock.MaxItems && done < this.count) { db = (!)((DirectBlock)idb[i]); if (ScanDirectBlockForId(db, Math.Min(this.count - done, DirectBlock.MaxItems), blockId, out dbIndex)) { absoluteIndex = done + dbIndex; return true; } done += stepSize; i++; } } else { int i = 0; while (i < IndirectBlock.MaxItems && done < this.count) { IndirectBlock! child = (IndirectBlock!) idb[i]; int childStepSize = stepSize / IndirectBlock.MaxItems; if (ScanIndirectBlockForId(depth - 1, child, done, childStepSize, blockId, out db, out dbIndex, out absoluteIndex)) { return true; } done += stepSize; i++; } } db = null; dbIndex = 0; absoluteIndex = 0; return false; } private bool FindId(int blockId, out DirectBlock db, out int dbIndex, out int absoluteIndex) { db = null; dbIndex = -1; absoluteIndex = -1; // NB need to watch end of index bounds if (ScanDirectBlockForId(this.direct, Math.Min(count, DirectBlock.MaxItems), blockId, out dbIndex)) { db = this.direct; absoluteIndex = dbIndex; return true; } if (this.count <= DirectBlock.MaxItems) { return false; } int done = DirectBlock.MaxItems; int step = DirectBlock.MaxItems; int depth = 0; while (done < this.count) { IndirectBlock! idb = (!)((IndirectBlock)this.indirects[depth]); if (ScanIndirectBlockForId( depth, idb, done, step, blockId, out db, out dbIndex, out absoluteIndex) ) { return true; } depth++; step *= IndirectBlock.MaxItems; done += step; } return false; } /// /// Replace bad block id with new block id. /// /// /// This method performs a linear search through the /// known block id's and replaces the target id if found. /// internal bool Replace(int oldBlockId, int newBlockId) { DirectBlock db; int dbIndex; int absIndex; if (FindId(oldBlockId, out db, out dbIndex, out absIndex)) { assert db != null; assert dbIndex >= 0 && dbIndex < DirectBlock.MaxItems; assert db[dbIndex] == oldBlockId; db[dbIndex] = newBlockId; return true; } return false; } /// /// Truncate at specified block id. Remove blockId and all block /// id's later in index. /// /// /// This method performs a linear search through /// the known block id's truncates the collection at the /// specified point if found. /// internal bool TruncateAtBlockId(int lastBlockId) { DirectBlock db; int dbIndex; int absIndex; if (FindId(lastBlockId, out db, out dbIndex, out absIndex)) { assert absIndex >= 0 && absIndex < count; assert db != null; assert db[dbIndex] == lastBlockId; this.count = absIndex; return true; } return false; } internal bool TruncateToLength(int clusterLength) requires clusterLength >= 0 && clusterLength <= this.Count; { this.count = clusterLength; return true; } /// /// Append blockId at end of index. /// /// The caller should check that file being added does not push /// the file size over FAT's maximum supported size (4GB). /// internal bool Append(int blockId) { if (this.count == this.maxCount) { return false; } int nthBlock = this.count; this.count++; // Check if we can place this in direct block if (nthBlock < DirectBlock.MaxItems) { if (this.direct == null) this.direct = new DirectBlock(); this.direct[nthBlock] = blockId; return true; } // Must be placed in an indirect block. Find which // indirect block to start search from. nthBlock -= DirectBlock.MaxItems; int threshold = IndirectBlock.MaxItems * DirectBlock.MaxItems; int depth = 0; while (nthBlock >= threshold) { nthBlock -= threshold; threshold *= IndirectBlock.MaxItems; depth++; } if (indirects[depth] == null) { indirects[depth] = new IndirectBlock(); } // Walk from root indirect block down to block containing // appropriate direct block. IndirectBlock ib = indirects[depth]; for (int i = 0; i < depth; i++) { int offset = IndirectOffset(depth, i, nthBlock); if (((!)ib)[offset] == null) { IndirectBlock tmp = new IndirectBlock(); ib[offset] = tmp; ib = tmp; } else { ib = (IndirectBlock)ib[offset]; } } // Allocate direct block if it does not exist int lastOffset = IndirectOffset(depth, depth, nthBlock); DirectBlock db; if (((!)ib)[lastOffset] == null) { db = new DirectBlock(); ib[lastOffset] = db; } else { db = (DirectBlock)ib[lastOffset]; } // Set entry in direct block and run. ((!)db)[DirectOffset(nthBlock)] = blockId; return true; } /// /// Append series of blockId's at end of index. /// /// true on success, false if capacity would be exceeded. /// internal bool Append(int startBlockId, int length) requires length >= 1; { if (this.count + length >= this.maxCount) { return false; } int done = 0; while (done != length) { if (this.Append(startBlockId + done) == false) { // This shouldn't happen. goto undo; } done++; } return true; undo: this.count -= done; return false; } #if TEST private static void TestLookup() { Console.WriteLine("Testing Lookup."); const int MaxLookupItems = 4 * 1024 * 1024; const int TickPeriod = 1000000; BlockIndex bi = new BlockIndex(); for (int i = 0; i < bi.maxCount; i++) { bi.Append(i); int j; bool r = bi.Lookup(i, out j); Debug.Assert(i == j); Debug.Assert(r == true); Debug.Assert(bi.Lookup(i + 1, out j) == false); if ((i % TickPeriod) == 0) Console.Write("."); } Debug.Assert(bi.Append(0) == false); for (int i = 0; i < MaxLookupItems; i++) { int u = bi.Count - i - 1; int j; bool r = bi.Lookup(u, out j); Debug.Assert(r == true); Debug.Assert(j == u); if ((i % TickPeriod) == 0) Console.Write("o"); } Debug.Assert(bi.Append(0) == false); for (int i = 0; i < MaxLookupItems; i++) { int u = (7 * i) % MaxLookupItems; int j; bool r = bi.Lookup(u, out j); Debug.Assert(r == true); Debug.Assert(j == u); if ((i % TickPeriod) == 0) Console.Write("O"); } Console.Write("\n"); } static private void TestTruncate() { Console.WriteLine("Testing Truncate."); const int MaxTruncateItems = 4 * 1024 * 1024; BlockIndex bi = new BlockIndex(); for (int i = 0; i < MaxTruncateItems; i++) { bi.Append(i); } Debug.Assert(!bi.TruncateAtBlockId(MaxTruncateItems)); int done = 0; int where = bi.Count - 1; do { Debug.Assert(where < bi.Count); bool r = bi.TruncateAtBlockId(where); Debug.Assert(r == true); Debug.Assert(bi.Count == where); done++; where = Math.Max((bi.Count * 97 / 100) - 1, 0); } while (bi.Count > 0); Debug.Assert(!bi.TruncateAtBlockId(MaxTruncateItems)); } static private void TestReplace() { Console.WriteLine("Testing Replace."); const int MaxReplaceItems = 4 * 1024 * 1024; BlockIndex bi = new BlockIndex(); for (int i = 0; i < MaxReplaceItems; i++) { bi.Append(i); } Debug.Assert(!bi.Replace(MaxReplaceItems, MaxReplaceItems)); int done = 0; int where = MaxReplaceItems - 1; while (where >= 0) { int newWhere = MaxReplaceItems + where; bi.Replace(where, newWhere); int checkWhere = 0; bi.Lookup(where, out checkWhere); Debug.Assert(checkWhere == newWhere); where = (where * 98 / 100) - 1; done++; } Debug.Assert(!bi.Replace(MaxReplaceItems, MaxReplaceItems)); } static public void Main() { TestTruncate(); TestReplace(); TestLookup(); } #endif // TEST } }