For my own amusement, I decided to wrap the NT Defrag APIs in C#.
It isn't great, but... I'll give it the classic dev guarantee of "it works on my machine". http://www.thenetworkadministrator.com/top20.htm
The MSDN documentation uses lots of very scary words like "IOCTL" and "DeviceIoControl" and the such. Hopefully, I've abstracted some of that away...
First, you can get a bitmap of the cluster usage on the device...
static public BitArray GetVolumeMap(string DeviceName)
Then you can get a map of a file - virtual clusters and logical clusters
static public Array GetFileMap(string path)
Finally, you can move sections of a file around on a volume...
static public void MoveFile(string deviceName, string path, Int64 VCN, Int64 LCN, Int32 count)
Here's the whole thing, nearly guaranteed to break your file system if used. :-)
//// a set of simple C# wrappers over the NT Defragmenter APIs//
//// Refrences
//// http://www.sysinternals.com/ntw2k/info/defrag.shtml// // msdn how-to// ms-help://MS.MSDNQTR.2004JUL.1033/fileio/base/defragmenting_files.htm// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/base/defragmenting_files.asp//// FSCTL_GET_VOLUME_BITMAP// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/base/fsctl_get_volume_bitmap.asp//// interesting structures...// FSCTL_MOVE_FILE// FSCTL_GET_RETRIEVAL_POINTERS// RETRIEVAL_POINTERS_BUFFER// FSCTL_GET_RETRIEVAL_POINTERS//// DeviceIoControl// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/devio/base/deviceiocontrol.asp//
using System;using System.Diagnostics;using System.Collections;using System.Runtime.InteropServices;
namespace defraglib{ public class IOWrapper {
// // CreateFile constants // const uint FILE_SHARE_READ = 0x00000001; const uint FILE_SHARE_WRITE = 0x00000002; const uint FILE_SHARE_DELETE = 0x00000004; const uint OPEN_EXISTING = 3;
const uint GENERIC_READ = (0x80000000); const uint GENERIC_WRITE = (0x40000000);
const uint FILE_FLAG_NO_BUFFERING = 0x20000000; const uint FILE_READ_ATTRIBUTES = (0x0080); const uint FILE_WRITE_ATTRIBUTES = 0x0100; const uint ERROR_INSUFFICIENT_BUFFER = 122;
[DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)] static extern int CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)] static extern bool DeviceIoControl( IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, [Out] IntPtr lpOutBuffer, uint nOutBufferSize, ref uint lpBytesReturned, IntPtr lpOverlapped);
static private IntPtr OpenVolume(string DeviceName) { IntPtr hDevice; hDevice = CreateFile( @"\\.\" + DeviceName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); if ((int)hDevice == -1) { throw new Exception(Marshal.GetLastWin32Error().ToString()); } return hDevice; }
static private IntPtr OpenFile(string path) { IntPtr hFile; hFile = CreateFile( path, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); if ((int)hFile == -1) { throw new Exception(Marshal.GetLastWin32Error().ToString()); } return hFile; }
/// <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; } }
/// <summary> /// returns a 2*number of extents array - /// the vcn and the lcn as pairs /// </summary> /// <param name="path">file to get the map for ex: "c:\windows\explorer.exe" </param> /// <returns>An array of [virtual cluster, physical cluster]</returns> static public Array GetFileMap(string path) { IntPtr hFile = IntPtr.Zero; IntPtr pAlloc = IntPtr.Zero;
try { hFile = OpenFile(path);
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( hFile, FSConstants.FSCTL_GET_RETRIEVAL_POINTERS, p, (uint)Marshal.SizeOf(i64), pDest, q, ref size, IntPtr.Zero);
if (!fResult) { throw new Exception(Marshal.GetLastWin32Error().ToString()); }
handle.Free();
/* returned back one of... typedef struct RETRIEVAL_POINTERS_BUFFER { DWORD ExtentCount; LARGE_INTEGER StartingVcn; struct { LARGE_INTEGER NextVcn; LARGE_INTEGER Lcn; } Extents[1]; } RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER; */
Int32 ExtentCount = (Int32)Marshal.PtrToStructure(pDest, typeof(Int32));
pDest = (IntPtr)((Int64)pDest + 4);
Int64 StartingVcn = (Int64)Marshal.PtrToStructure(pDest, typeof(Int64));
Debug.Assert(StartingVcn == 0);
pDest = (IntPtr)((Int64)pDest + 8);
// now pDest points at an array of pairs of Int64s.
Array retVal = Array.CreateInstance(typeof(Int64), new int[2] { ExtentCount, 2 });
for (int i = 0; i < ExtentCount; i++) { for (int j = 0; j < 2; j++) { Int64 v = (Int64)Marshal.PtrToStructure(pDest, typeof(Int64)); retVal.SetValue(v, new int[2] { i, j }); pDest = (IntPtr)((Int64)pDest + 8); } }
return retVal; } finally { CloseHandle(hFile); hFile = IntPtr.Zero;
/// <summary> /// input structure for use in MoveFile /// </summary> private struct MoveFileData { public IntPtr hFile; public Int64 StartingVCN; public Int64 StartingLCN; public Int32 ClusterCount; }
/// <summary> /// move a virtual cluster for a file to a logical cluster on disk, repeat for count clusters /// </summary> /// <param name="deviceName">device to move on"c:"</param> /// <param name="path">file to muck with "c:\windows\explorer.exe"</param> /// <param name="VCN">cluster number in file to move</param> /// <param name="LCN">cluster on disk to move to</param> /// <param name="count">for how many clusters</param> static public void MoveFile(string deviceName, string path, Int64 VCN, Int64 LCN, Int32 count) { IntPtr hVol = IntPtr.Zero; IntPtr hFile = IntPtr.Zero; try { hVol = OpenVolume(deviceName);
hFile = OpenFile(path);
MoveFileData mfd = new MoveFileData(); mfd.hFile = hFile; mfd.StartingVCN = VCN; mfd.StartingLCN = LCN; mfd.ClusterCount = count;
GCHandle handle = GCHandle.Alloc(mfd, GCHandleType.Pinned); IntPtr p = handle.AddrOfPinnedObject(); uint bufSize = (uint)Marshal.SizeOf(mfd); uint size = 0;
bool fResult = DeviceIoControl( hVol, FSConstants.FSCTL_MOVE_FILE, p, bufSize, IntPtr.Zero, // no output data from this FSCTL 0, ref size, IntPtr.Zero);
if (!fResult) { throw new Exception(Marshal.GetLastWin32Error().ToString()); } } finally { CloseHandle(hVol); CloseHandle(hFile); } } }
/// <summary> /// constants lifted from winioctl.h from platform sdk /// </summary> internal class FSConstants { const uint FILE_DEVICE_FILE_SYSTEM = 0x00000009;
const uint METHOD_NEITHER = 3; const uint METHOD_BUFFERED = 0;
const uint FILE_ANY_ACCESS = 0; const uint FILE_SPECIAL_ACCESS = FILE_ANY_ACCESS;
public static uint FSCTL_GET_VOLUME_BITMAP = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 27, METHOD_NEITHER, FILE_ANY_ACCESS); public static uint FSCTL_GET_RETRIEVAL_POINTERS = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 28, METHOD_NEITHER, FILE_ANY_ACCESS); public static uint FSCTL_MOVE_FILE = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 29, METHOD_BUFFERED, FILE_SPECIAL_ACCESS);
static uint CTL_CODE(uint DeviceType, uint Function, uint Method, uint Access) { return ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method); } }
}