DOS to NT: A Path's Journey


Most of us are familiar with DOS style paths to files such as C:\bar\foo.txt. Most aren't familiar with the DOS Device format of said path: \\.\C:\bar\foo.txt. Even less know about the lowest level version of this path- the "real" NT path to the file object that Windows uses internally: \Device\HarddiskVolume4\bar\foo.txt.

Knowing how paths resolve through the system will help you understand how to access all resources (such as volumes that don't have a drive letter) and write any path handling code correctly. My prior post on Path Normalization is one part of this. This post will follow the path's resolution to help you understand why all of the following (and many other variants) are equivalent:

  • C:\bar\foo.txt
  • \\.\C:\bar\foo.txt
  • \\?\C:\bar\foo.txt
  • \??\C:\bar\foo.txt
  • \\?\Harddisk0Partition2\bar\foo.txt
  • \\?\HarddiskVolume4\bar\foo.txt
  • \\?\Volume{0FAF43C1-2AED-4824-B2D4-2339C14E93A}\bar\foo.txt
  • \\?\Volume{d1655348-000-000-000-f0150000000}\bar\foo.txt
  • \\?\GLOBALROOT\Device\Harddisk0\Partition2\bar\foo.txt
  • \\.\GLOBALROOT\Device\HarddiskVolume4\bar\foo.txt
  • \\LOCALHOST\C$\bar\foo.txt
  • \\?\UNC\C$\bar\foo.txt
  • \\?\GLOBALROOT\Device\Mup\C$\bar\foo.txt

The "Real" Path

The "real" path would be the path Windows uses internally- or the "NT path".  An NT path is a path to a resource as maintained by the Windows Object Manager. The Object Manager handles creation, destruction, and access rights for objects. Resources include files, registry keys, threads, processes, devices, mutexes and others.

An NT path for a file is a path to a device in the Object Manager namespace followed by the local path for the given device. C:\bar\foo.txt on my machine is the NT path \Device\HarddiskVolume4\bar\foo.txt. \Device\HarddiskVolume4 on my machine is the device object that is exposed to Win32 by the symbolic link for my drive (C:). The device object is where the Object Manager's parsing of the path is handed off. The rest of the path \bar\foo.txt is handed off to the I/O Manager and the relevant driver(s) for further parsing and object creation.

A Path in Transition

To further illustrate the process, lets follow a path as it goes through the system.

C:\bar>echo foo > foo.txt

A file object needs to be opened and a handle to it returned in order to get the data out of the file (via CreateFile).  "foo.txt" is passed to CreateFile and this is roughly what happens:

  1. foo.txt is normalized, becoming C:\bar\foo.txt.
  2. C:\bar\foo.txt is transformed into its NT path equivalent, \??\C:\bar\foo.txt.
  3. The DOS device path is recognized (\??) and an appropriate entry is looked for in the local session entries (something like \Sessions\DosDevices) and then the global DOS device directory \GLOBAL??.
  4. The entry for C: is found in \GLOBAL?? which symlinks to \Device\HarddiskVolume4.
  5. \Device\HarddiskVolume4 is found to be a device leaf node by the Object Manager
  6. The rest of the path \bar\foo.txt is passed to the I/O Manager (which owns the device object type) along with a pointer to the specific device object.
  7. The I/O Manager identifies the driver associated with the specified device and makes the request to the driver to open the desired file object

Exploring the Object Manager

The Sysinternals tool "WinObj" will let you browse the Windows object manager. If we look for the device path in the example above, we'll find the following:

[caption id="attachment_435" align="alignnone" width="806"]A device path in the object manager. A device path in the object manager.[/caption]

Following the symbolic link will take us to the Device leaf node.

[caption id="attachment_445" align="alignnone" width="806"]The Device object the C: symbolic link points to. The Device object the C: symbolic link points to.[/caption]

(Note that you need to run the tool as Administrator to see some of the objects, but not the ones we care about here.)

What Was That

The \??\C:\foo.txt form is how the path is passed to the Object Manager (and how you would represent a path when writing a kernel mode driver). Some Win32 APIs recognize this form of the path, but not all of them. Use it for giggles, but not production code as the APIs that don't recognize it will fall through to normalization- call GetFullPathName() on this particular path to see what will happen or try and reason it out yourself using the information in my last post.

Up Next

Other related topics that I intend to go over are common mistakes made with paths and utilizing the various sets of APIs related to these recent posts on paths. Feel free to chime in with suggestions about what you want to see me cover.