0

I'm writing a program that copies the FAT to a file (and restores it). I'm using CreateFile to open a drive letter as a file, SetFilePointerEx to seek to 0 position, ReadFile to read the contents of the drive, and WriteFile to write to the drive.

Using this strategy I can basically copy the entire drive. However, how should I know where to start and when to stop? Basically, what I want to know is the location of the beginning and the end of File Allocation Table in an exFAT disk.

Here's the code that I use to run the backup for the first 4 GB of data:


    private static void RunBackup(string driveLetter)
    {
        IntPtr handle = CreateFile(
            string.Format("\\\\.\\{0}:", driveLetter),
            FileAccess.Read,
            FileShare.Read,
            IntPtr.Zero,
            (FileMode)OPEN_EXISTING,
            0,
            IntPtr.Zero);
    
        // Set offset
        uint chunks = 100;
        uint bufferSize = 512 * chunks;
        long pt = 0;
        byte[] buffer = new byte[bufferSize];
        SetFilePointerEx(
            handle,
            0,
            ref pt,
            0);
    
        long oneGB = 1073741824;
        var backupSize = oneGB * 4;
        var loops = backupSize / bufferSize;
    
        Console.WriteLine($"Expecting {loops:N0} loops.");
    
        uint read = 0;
        using (var writer = new BinaryWriter(File.OpenWrite(@"D:\\fat.backup")))
        {
            for (int i = 0; i < loops; i++)
            {
                ReadFile(
                    handle,
                    buffer,
                    bufferSize,
                    ref read,
                    IntPtr.Zero);
    
                writer.Write(buffer);
                writer.Flush();
    
                Console.Write($"\rLoop: {i:N0}");
            }
    
            writer.Close();
        }
    
        CloseHandle(handle);
    }

Alireza Noori
  • 14,961
  • 30
  • 95
  • 179

1 Answers1

3

Reading the exFAT specification is a good start.

In §2 ‘Volume structure’, we have a table:

Sub-region Name Offset (sector) Size (sectors)
First FAT FatOffset FatLength
Second FAT FatOffset + FatLength FatLength × (NumberOfFats − 1)

FatOffset, FatLength and NumberOfFats are fields of the boot sector, as described in §3.1 ‘Main and Backup Boot Sector Sub-regions’:

Field Name Offset (byte) Size (bytes)
FatOffset 80 4
FatLength 84 4
NumberOfFats 110 1

The values are in sector units, so you will have to multiply them by the sector size before calling SetFilePointerEx. The sector size can be obtained from the DeviceIoControl call IOCTL_DISK_GET_DRIVE_GEOMETRY_EX; the returned DISK_GEOMETRY_EX structure contains a Geometry.BytesPerSector (nested) field. The FatOffset and FatLength values are little endian, so you will have to decode them with a function like:

private static uint ReadLE32(byte[] data, uint offset)
{
    return (data[offset + 3] << 24)
         | (data[offset + 2] << 16)
         | (data[offset + 1] << 8)
         | data[offset];
}

It’s also typical to read the whole boot sector to extract information from it, not individual fields. Remember also that any of the Windows API calls may return an error: if you worry about reliability, you should be checking return values and convert them into exceptions as necessary.

Putting it all together: you open the disk as before, read its boot sector, discover the offset and size of the FAT, then seek to the FAT and read it off.

user3840170
  • 26,597
  • 4
  • 30
  • 62
  • Thanks for the answer. Please check my edit. I posted some of my code. Based on that, please explain a little more in detail what I should do. Is the current method to open the drive OK? Should I only read 2 bytes and compute the result? – Alireza Noori Aug 03 '21 at 18:33
  • @AlirezaNoori You don’t check for errors, which I assume may be returned as return values by the native Windows APIs (unless C# is somehow converting them into exceptions). – user3840170 Aug 03 '21 at 18:44
  • @AlirezaNoori As for how to read the values from the boot sector, see . Best to read the whole boot sector, then decode the values at the specified offsets of the boot sector. Then seek to the first sector of the FAT (having multiplied `FatOffset` by the sector size) and read it off. – user3840170 Aug 03 '21 at 18:48
  • Sorry. Now I'm more confused. When I open the disk using `CreateFile` and `"\\\\.\\J:"` as path, is the first byte, the start of boot sector? So if I seek to byte number 80 and read 4 bytes, it would be `FatOffset` and the next 4 bytes would be `FatLength`. Am I correct? – Alireza Noori Aug 03 '21 at 19:19
  • So basically I should call `SetFilePointerEx` like this `SetFilePointerEx(handle, 80, ref pt,0);` and read 8 bytes. The first 4 would be `FatOffset` and the next 4 bytes would be `FatLength` – Alireza Noori Aug 03 '21 at 19:21
  • And if I read these two numbers, where should I get the number of FATs? Should I use the windows properties for the drive or can I read them somewhere else? Could you please give me the exact formula. This is a little too sensitive for me to experiment. Thanks – Alireza Noori Aug 03 '21 at 19:23