3

For a project, I want to get the list of all free/used clusters on an NTFS partition. For this i have to dump the $Bitmap file and parse its contents.

There are few API's and examples on the web, but however they don't seem to work. Is there a simple way/ code sample to just copy the $Bitmap file somewhere.

Is using FSCTL_GET_VOLUME_BITMAP the only way? Ideally I would like to do it in C#.

Roman R.
  • 68,205
  • 6
  • 94
  • 158
Vikram
  • 41
  • 1
  • 4
  • 2
    And here I thought `FSCTL_GET_VOLUME_BITMAP` *was* the easy way... – Gabe Apr 25 '12 at 06:18
  • 1
    I hope you realize that unless the filesystem is TOTALLY idle, the second you retrieve the bitmap, it is out of date... – MJZ Nov 14 '12 at 21:53
  • @MJZ but reading the file would make the partition _not_ idle. The partition has to be unmounted. – Cole Tobin Jun 21 '13 at 12:52
  • Or lock it for exclusive access. Then you can use the FSCTL. Otherwise, you have to read and parse the file system structures from the raw disk blocks. Which, of course, MSFT has declined to document.[edit fixed a typo] – MJZ Aug 04 '13 at 22:22

2 Answers2

2

NFI.EXE which is (used to be) part of the "oem support tools" can enumerate all NTFS partition items. It might also be capable to dump the content of $Bitmap.

enter image description here

mox
  • 6,084
  • 2
  • 23
  • 35
1

You definitely want to go the easy route and use the IOCTL rather than trying to read $Bitmap directly. Of course, you don't have to do it yourself if somebody has done it for you. It turns out that an MSDN blogger has already written a nice little wrapper for you:

http://blogs.msdn.com/jeffrey_wall/archive/2004/09/13/229137.aspx

The whole class is over 300 lines of code, so I won't post it all, but here's the function that gets the volume bitmap:

    /// <summary>
    /// Get cluster usage for a device
    /// </summary>
    /// <param name="DeviceName">use "c:"</param>
    /// <returns>a bitarray for each cluster</returns>
    static public BitArray GetVolumeMap(string DeviceName)
    {
        IntPtr pAlloc = IntPtr.Zero;
        IntPtr hDevice = IntPtr.Zero;

        try
        {
            hDevice = OpenVolume(DeviceName);

            Int64 i64 = 0;

            GCHandle handle = GCHandle.Alloc(i64, GCHandleType.Pinned);
            IntPtr p = handle.AddrOfPinnedObject();

            // alloc off more than enough for my machine
            // 64 megs == 67108864 bytes == 536870912 bits == cluster count
            // NTFS 4k clusters == 2147483648 k of storage == 2097152 megs == 2048 gig disk storage
            uint q = 1024 * 1024 * 64; // 1024 bytes == 1k * 1024 == 1 meg * 64 == 64 megs

            uint size = 0;
            pAlloc = Marshal.AllocHGlobal((int)q);
            IntPtr pDest = pAlloc;

            bool fResult = DeviceIoControl(
                hDevice,
                FSConstants.FSCTL_GET_VOLUME_BITMAP,
                p,
                (uint)Marshal.SizeOf(i64),
                pDest,
                q,
                ref size,
                IntPtr.Zero);

            if (!fResult)
            {
                throw new Exception(Marshal.GetLastWin32Error().ToString());
            }
            handle.Free();

            /*
            object returned was...
      typedef struct 
      {
       LARGE_INTEGER StartingLcn;
       LARGE_INTEGER BitmapSize;
       BYTE Buffer[1];
      } VOLUME_BITMAP_BUFFER, *PVOLUME_BITMAP_BUFFER;
            */
            Int64 StartingLcn = (Int64)Marshal.PtrToStructure(pDest, typeof(Int64));

            Debug.Assert(StartingLcn == 0);

            pDest = (IntPtr)((Int64)pDest + 8);
            Int64 BitmapSize = (Int64)Marshal.PtrToStructure(pDest, typeof(Int64));

            Int32 byteSize = (int)(BitmapSize / 8);
            byteSize++; // round up - even with no remainder

            IntPtr BitmapBegin = (IntPtr)((Int64)pDest + 8);

            byte[] byteArr = new byte[byteSize];

            Marshal.Copy(BitmapBegin, byteArr, 0, (Int32)byteSize);

            BitArray retVal = new BitArray(byteArr);
            retVal.Length = (int)BitmapSize; // truncate to exact cluster count
            return retVal;
        }
        finally
        {
            CloseHandle(hDevice);
            hDevice = IntPtr.Zero;

            Marshal.FreeHGlobal(pAlloc);
            pAlloc = IntPtr.Zero;
        }
    }
mirh
  • 514
  • 8
  • 14
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • @Gabe code from the link, so it isn't a borderline "link only answer". But seeing how big the code block is, never mind. – Cole Tobin May 23 '13 at 06:45
  • 1
    @Cole: Yeah, I counted 380 lines! – Gabe May 23 '13 at 06:47
  • It looks like the original link has suffered from one of MSDN's many reorgs. You can access it here through the Wayback Machine: https://web.archive.org/web/20120221120423/http://blogs.msdn.com/b/jeffrey_wall/archive/2004/09/13/229137.aspx – Dave R. Apr 07 '21 at 14:46