Needful Software

Raw Disk Access

Raw Disk Access (2/2)

Reading sectors from a drive

In this example we will read a few sectors from the C: drive. It is available in the download package in the RawDiskAccess1 folder. It needs administrative privileges to run successfully. On Windows Vista it should be run as administrator.

Here is an outline of what the example does:

  1. Get a handle to the C: drive by using CreateFile.
  2. Get the sector size with GetDiskFreeSpace.
  3. Allocate a buffer with VirtualAlloc. The data will be read into this buffer.
  4. Position the file pointer at the start of the sector(s) we want to read using SetFilePointer.
  5. Read the entire sector(s) in the buffer using ReadFile.
The example as it is reads the first two sectors of the C: drive but you can easily read any sector by modifying n1 and n2 appropriately.

We will now describe each of these steps in detail. The example is made of only one file called RawDiskAccess1.cpp but on this page we did split it into several parts so that we can describe each part separately. You can get the entire file simply by concatenating the snippets but it is probably easier to just get it from the download page.

Getting the handle to the C: drive with CreateFile

The first step is to use CreateFile to get a handle to the C: drive. Don't be confused by the fact that we use the CreateFile function do this. It has not much to do with opening a file but it just happens that the CreateFile functions is also used to get handles to other objects than files (including the console...).

The fact that a drive has to be opened rather than a simple file is indicated by using a specific format for the path passed to CreateFile. To open a physical drive the format must be \\.\PhysicalDriveX where X is the number of the drive. To open a volume or removable media drive the format must be \\.\X: where X is the driver letter. There must be no backslash at the end. If there is a trailing backslash the root directory of that drive will be opened rather than the drive itself. In this example we will use the second format because we want to open the C drive.

To get more details I recommend you read the documentation for CreateFile on MSDN [1], in particular the section called "Physical Disks and Volumes".

The code that gets a handle to the C: drive is reproduced below. We open the C: drive by passing in the specially formatted path "\\.\C:" (with double backslash in the code as in C/C++ we must use the '\\' escape sequence to produce a backslash). As you can see we specify the options FILE_SHARE_WRITE and OPEN_EXISTING. These options are mandatory when opening a drive. In this example we also specify GENERIC_READ and FILE_SHARE_READ as we want to read from the disk.

You will also notice that the FILE_FLAG_NO_BUFFERING. This is explained in the next section.

#include <windows.h>
#include <stdio.h>

void PrintLastError();

int main(int argc, char* argv[])
{
   // Administrative privileges are required to be able to do this
   HANDLE hRawDisk = CreateFile("\\\\.\\C:", // Must be exactly in this format
      GENERIC_READ,
      FILE_SHARE_READ | FILE_SHARE_WRITE, // Must have FILE_SHARE_WRITE
      NULL,
      OPEN_EXISTING, // Must be OPEN_EXISTING
      FILE_FLAG_NO_BUFFERING, // Access will not be buffered even if this flag
                              // is set so it is less confusing to specify it
                              // explicitly
      NULL);

   if (hRawDisk == INVALID_HANDLE_VALUE)
   {
      printf("CreateFile failed\r\n");
      PrintLastError();
      return -1;
   }

Constraints due to the FILE_FLAG_NO_BUFFERING option

A drive will always be opened as if the FILE_FLAG_NO_BUFFERING option was specified. For instance in the code above we could have omitted it but it still would be applied implicitly.

This option puts a few constraints on how the returned handle must be accessed. The constraints are listed in [2]. I reproduce them here for convenience.

  1. File access sizes must be for a number of bytes that is an integer multiple of the volume sector size. For example, if the sector size is 512 bytes, an application can request reads and writes of 512, 1,024, 1,536, or 2,048 bytes, but not of 335, 981, or 7,171 bytes.
  2. File access buffer addresses for read and write operations should be sector-aligned, which means aligned on addresses in memory that are integer multiples of the volume sector size. Depending on the disk, this requirement may not be enforced.

To make sure we comply with these constraints we will get the sector size using the GetDiskFreeSpace function and allocate a properly aligned buffer with VirtualAlloc. The VirtualAlloc technique is mentioned in [2] but only works if the page size is bigger than the sector size whih is almost always the case. An alternate method is to use _aligned_malloc, also mentioned in [2].

The code below shows how to use GetDiskFreeSpace and VirtualAlloc.

   // Because the FILE_FLAG_NO_BUFFERING is used (no matter whether you
   // explicitly specify it or not), access must be aligned with sector
   // size so we use GetDiskFreeSpace to get the sector size
   DWORD sectorsPerCluster;
   DWORD bytesPerSector;
   DWORD numberOfFreeClusters;
   DWORD totalNumberOfClusters;
   if (!GetDiskFreeSpace("C:\\", &sectorsPerCluster, &bytesPerSector,
      &numberOfFreeClusters, &totalNumberOfClusters))
   {
      printf("GetDiskFreeSpace failed\r\n");
      PrintLastError();
      CloseHandle(hRawDisk);
      return -2;
   }

   // As recommended in MSDN we use VirtualAlloc to get a buffer that is
   // aligned with the sector size
   // We are going to read n2 sectors
   int n2 = 2;
   LPVOID buffer = VirtualAlloc(NULL, n2 * bytesPerSector, MEM_COMMIT,
      PAGE_READWRITE);
   if (buffer == NULL)
   {
      printf("VirtualAlloc failed\r\n");
      PrintLastError();
      CloseHandle(hRawDisk);
      return -3;
   }

Reading

We now have everything we need to read data from the drive. Reading is done using the usual SetFilePointer and ReadFile functions but do not forget the constraints listed in the previous section. The code below shows how this can be done. In real code you'd replace the "printf("Success!\r\n");" with something more useful of course.

   DWORD bytesRead;

   // Because the FILE_FLAG_NO_BUFFERING is used (no matter whether you
   // explicitly specify it or not), access must be aligned with sector
   // size so we can only set the file pointer at a position that is a
   // multiple of the sector size
   // We are going to start reading from sector n1 (first sector is sector 0)
   int n1 = 0;
   DWORD r = SetFilePointer(hRawDisk, n1 * bytesPerSector, NULL, FILE_BEGIN);
   if (r == INVALID_SET_FILE_POINTER)
   {
      printf("SetFilePointer failed\r\n");
      PrintLastError();
      VirtualFree(buffer, 0, MEM_RELEASE);
      CloseHandle(hRawDisk);
      return -4;
   }

   // Because the FILE_FLAG_NO_BUFFERING is used (no matter whether you
   // explicitly specify it or not), access must be aligned with sector
   // size so we can only read a number of bytes that is a multiple of
   // the sector size
   if (ReadFile(hRawDisk, buffer, n2 * bytesPerSector, &bytesRead, NULL))
   {
      printf("Success!\r\n");
   }
   else
   {
      printf("ReadFile failed\r\n");
      PrintLastError();
      VirtualFree(buffer, 0, MEM_RELEASE);
      CloseHandle(hRawDisk);
      return -5;
   }

   VirtualFree(buffer, 0, MEM_RELEASE);
   CloseHandle(hRawDisk);
   return 0;
}

PrintLastError

For completeness we show the code for the PrintLastError utility function. It simply uses GetLastError to get the error code and prints the associated error message to the standard output.

void PrintLastError()
{
   LPVOID lpMsgBuf;
   if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
      FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(),
      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL))
   {
      printf((char*)lpMsgBuf);
      LocalFree(lpMsgBuf);
   }
}

Errors

We mentioned the constraints imposed by the FILE_FLAG_NO_BUFFERING option. If we ignored these constraints and tried to use SetFilePointer with a file offset that is not a multiple of the sector size or ReadFile with a number of bytes to read that is not a multiple of the sector size, PrintLastError would print this message: "The parameter is incorrect." You can easily try it by modifying the example above.

An error that may not cause any problems though is if the buffer is not sector-aligned as the documentation says that this is not always enforced but obviously it is better to comply with the constraint to avoid any issues.

Bibliography

blog comments powered by Disqus