/* Copyright (C) 2014 Tal Aloni . All rights reserved. * * You can redistribute this program and/or modify it under the terms of * the GNU Lesser Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. */ using System; using System.Collections.Generic; using System.IO; using System.Text; using Utilities; namespace DiskAccessLibrary.FileSystems.NTFS { public class ClusterUsageBitmap : NTFSFile { NTFSVolume m_volume; private long m_bufferedClusterVCN = -1; private byte[] m_bufferedCluster; private bool m_isBufferDirty; // if set to true, we need to write the buffer public ClusterUsageBitmap(NTFSVolume volume) : base(volume, MasterFileTable.BitmapSegmentNumber) { m_volume = volume; } private byte[] GetBitmapCluster(long bitmapClusterVCN) { if (m_bufferedClusterVCN != bitmapClusterVCN) { if (m_isBufferDirty) { FlushBuffer(); } m_bufferedClusterVCN = bitmapClusterVCN; // ClusterUsageBitmap is always non-resident m_bufferedCluster = FileRecord.NonResidentDataRecord.ReadDataCluster(Volume, bitmapClusterVCN); } return m_bufferedCluster; } private void FlushBuffer() { FileRecord.NonResidentDataRecord.WriteDataClusters(Volume, m_bufferedClusterVCN, m_bufferedCluster); m_isBufferDirty = false; } /// Logical cluster number private bool IsClusterFree(ulong lcn) { long bitmapClusterVCN = ((long)(lcn / 8)) / Volume.BytesPerCluster; byte[] bitmap = GetBitmapCluster(bitmapClusterVCN); int lcnByteOffset = (int)(((long)(lcn / 8)) % Volume.BytesPerCluster); int lcnBitIndex = (int)(lcn % 8); bool isInUse = ((bitmap[lcnByteOffset] >> lcnBitIndex) & 0x01) != 0; return !isInUse; } private void UpdateClusterStatus(ulong lcn, bool isUsed) { long bitmapClusterVCN = (long)(lcn / (8 * (uint)Volume.BytesPerCluster)); // bitmap will reference m_bufferedCluster byte[] bitmap = GetBitmapCluster(bitmapClusterVCN); ulong clusterIndexInBitmap = lcn % (8 * (uint)Volume.BytesPerCluster); UpdateClusterStatus(bitmap, clusterIndexInBitmap, true); m_isBufferDirty = true; } public KeyValuePairList AllocateClusters(ulong desiredStartLCN, long numberOfClusters) { KeyValuePairList freeClusterRunList = FindClustersToAllocate(desiredStartLCN, numberOfClusters); // mark the clusters as used in the volume bitmap for (int index = 0; index < freeClusterRunList.Count; index++) { ulong runStartLCN = freeClusterRunList[index].Key; long runLength = freeClusterRunList[index].Value; for (uint position = 0; position < runLength; position++) { UpdateClusterStatus(runStartLCN + position, true); } } if (m_isBufferDirty) { FlushBuffer(); } return freeClusterRunList; } /// /// Return list of free cluster runs, key is cluster LCN, value is run length /// private KeyValuePairList FindClustersToAllocate(ulong desiredStartLCN, long numberOfClusters) { KeyValuePairList result = new KeyValuePairList(); long leftToAllocate; ulong endLCN = (ulong)(m_volume.Size / m_volume.BytesPerCluster) - 1; KeyValuePairList segment = FindClustersToAllocate(desiredStartLCN, endLCN, numberOfClusters, out leftToAllocate); result.AddRange(segment); if (leftToAllocate > 0 && desiredStartLCN > 0) { segment = FindClustersToAllocate(0, desiredStartLCN - 1, leftToAllocate, out leftToAllocate); result.AddRange(segment); } if (leftToAllocate > 0) { throw new Exception("Could not allocate clusters. Not enough free disk space"); } return result; } /// Number of clusters to allocate private KeyValuePairList FindClustersToAllocate(ulong startLCN, ulong endLCN, long clustersToAllocate, out long leftToAllocate) { KeyValuePairList result = new KeyValuePairList(); leftToAllocate = clustersToAllocate; ulong runStartLCN = 0; // temporary long runLength = 0; ulong nextLCN = startLCN; while (nextLCN <= endLCN && leftToAllocate > 0) { if (IsClusterFree(nextLCN)) { if (runLength == 0) { runStartLCN = nextLCN; runLength = 1; } else { runLength++; } leftToAllocate--; } else { if (runLength > 0) { result.Add(runStartLCN, runLength); runLength = 0; // add this run } } nextLCN++; } // add the last run if (runLength > 0) { result.Add(runStartLCN, runLength); } return result; } private byte CountNumberOfFreeClusters(byte bitmap) { byte result = 0; for (int bitIndex = 0; bitIndex < 8; bitIndex++) { bool isFree = ((bitmap >> bitIndex) & 0x01) == 0; if (isFree) { result++; } } return result; } // Each bit in the $Bitmap file represents a cluster. // The size of the $Bitmap file is always a multiple of 8 bytes, extra bits are always set to 1. public long CountNumberOfFreeClusters() { int transferSizeInClusters = Settings.MaximumTransferSizeLBA / m_volume.SectorsPerCluster; ulong endLCN = (ulong)(m_volume.Size / m_volume.BytesPerCluster) - 1; long lastClusterVCN = ((long)(endLCN / 8)) / Volume.BytesPerCluster; long result = 0; // build lookup table byte[] lookup = new byte[256]; for (int index = 0; index < 256; index++) { lookup[index] = CountNumberOfFreeClusters((byte)index); } // extra bits will be marked as used, so no need for special treatment for (long bitmapVcn = 0; bitmapVcn < lastClusterVCN; bitmapVcn += transferSizeInClusters) { byte[] bitmap = FileRecord.NonResidentDataRecord.ReadDataClusters(Volume, bitmapVcn, transferSizeInClusters); for (int index = 0; index < bitmap.Length; index++) { result += lookup[bitmap[index]]; } } return result; } public static void UpdateClusterStatus(byte[] bitmap, ulong clusterIndexInBitmap, bool isUsed) { ulong byteOffset = (ulong)clusterIndexInBitmap / 8; int bitOffset = (int)clusterIndexInBitmap % 8; if (isUsed) { bitmap[byteOffset] |= (byte)(0x01 << bitOffset); } else { bitmap[byteOffset] &= (byte)(~(0x01 << bitOffset)); } } } }