Creating symbolic links for an anonymous FDO or filter

As I wrote about previously, naming your FDO has some side effects that you may not want to incur. But what if you want to give your device a fixed symbolic link name (by calling IoCreateSymbolicLink), such as \DosDevices\Foo1, in addition to your device interface GUID? Does an unnamed FDO (or filter exclude the possibility of creating the fixed symbolic link? The short answer is that it is still possible to create a fixed symbolic link for any unnamed device in a PnP stack, you just have a little more work ahead of you if you are writing a WDM driver (KMDF does implements the recommendation in this post for you). First, let's take a look at how you would create a fixed symbolic link name if your had a named device

 DECLARE_CONST_UNICODE_STRING(deviceName, L"\\Device\\Foo1");
DECLARE_CONST_UNICODE_STRING(symbolicLinkName, L"\\DosDevices\Foo1");

IoCreateDevice(..., &deviceName, ...);
IoCreateSymbolicLinkName(&symbolicLinkName, &deviceName);

The key takeaway here is that the symbolic link name points to some device's name. If you had an unnamed device object, you have no device name to pass to IoCreateSymbolicLink. The saving grace here is that at least one device in the stack is guaranteed to have a name, the PDO. All you need to do is to get the name of the PDO and create a symbolic link against the PDO's name. IoGetDeviceProperty(DevicePropertyPhysicalDeviceObjectName) will give us the PDO name (as would ObQueryNameString, but only until recently this was only documented in the IFS kit). To retrieve the name you must call IoGetDeviceProperty twice, first to get the length of the name buffer and second to retrieve the name. Here is how you would retrieve the PDO name and create the symbolic link against it (feel free to reuse the code in your own driver).

 PDEVICE_OBJECT pPdo;
ULONG length;
NTSTATUS status;
PWSTR pName;
UNICODE_STRING pdoName;

pPdo = DeviceExtension->PhysicalDeviceObject;
length = 0;

status = IoGetDeviceProperty(pPdo,
                             DevicePropertyPhysicalDeviceObjectName,
                             0,
                             NULL,
                             &length);

if (status != STATUS_BUFFER_TOO_SMALL && !NT_SUCCESS(status)) {
    return status;
}
else if (length > USHORT_MAX) {
    return STATUS_INTEGER_OVERFLOW;
}
else if (length == 0) {
    //
    // We can get zero back if the PDO is being deleted.  This can only happen if we are 
    // creating the symbolic link outside of AddDevice() or a PnP start.
    //
    return STATUS_INVALID_DEVICE_STATE;
}

pName = (PWSTR) ExAllocatePoolWithTag(PagedPool, length,  [tag] );
if (pName == NULL) {
    return STATUS_INSUFFICIENT_RESOURCES;
}

status = IoGetDeviceProperty(pPdo,
                             DevicePropertyPhysicalDeviceObjectName,
                             length,
                             pName,
                             &length);

if (!NT_SUCCESS(status)) {
    ExFreePool(pName);
    pName = NULL;
    return status;
}

pdoName.Buffer = pName;
pdoName.Length = (USHORT) length - sizeof(UNICODE_NULL);
pdoName.MaximumLength = (USHORT) length;

status = IoCreateSymbolicLink( [SymbolicLinkName] , &pdoName)

if (NT_SUCCESS(status)) {
    // the type of DeviceExtension->PdoName is UNICODE_STRING
    RtlCopyMemory(&DeviceExtension->PdoName, &pdoName, sizeof(pdoName));

    // now that we stored the buffer in our extension, remove all references to the buffer
    // so that we don't accidentally free it while still pointing to it in the extension
    pName = NULL;
    RtlZeroMemory(&pdoName, sizeof(pdoName));
}
else {
    ExFreePool(pName);
    pName = NULL;
}

return status;

Remember that you must also destroy the symbolic on a surprise remove or graceful remove which is why I stored the PDO name in the device extension. Otherwise, you would have to query, allocate and requery during remove processing, which just makes your driver more complicated (and if the alloc fails, there is no way for you to destroy the symbolic link name, so by storing it upon creation, we can guarantee we can destroy it later). Also, remember to free DeviceExtension->PdoName.Buffer during remove processing when you are freeing resources! Again, KMDF does all of this for you (including the automatic destroying of the link upon deletion), all you have to do is create an unnamed WDFDEVICE and then call WdfDeviceCreateSymbolicLink and you are done.

If you think about this a little more deeply, this is exactly how device interfaces work. When you create a device interface, the OS creates a symbolic link name on your behalf and has it point to the PDO's name. The difference between your symbolic link name and the OS generated name based on the device interface GUID is that the device interface has built in mechanisms to notify an application of arrival or departure and has a name which can be queried for at runtime. The fixed symbolic link has no such notifications and there is no way to tell how many of these links exist.

Special thanks to Mark Roddy who intially suggested this technique during KMDF beta testing.