How can I convert a native (NT) pathname into a Win32 path name?
Asked Answered
M

7

12

I'm working on reporting some information gleaned from native system APIs. (I know this is bad.... but I'm getting information that I can't get otherwise, and I have little issue with having to update my app if/when that time comes around.)

The native API returns native pathnames, as seen by ob, i.e. \SystemRoot\System32\Ntoskrnl.exe, or \??\C:\Program Files\VMWare Workstation\vstor-ws60.sys.

I can replace common prefixes, i.e.

std::wstring NtPathToWin32Path( std::wstring ntPath )
{
    if (boost::starts_with(ntPath, L"\\\\?\\"))
    {
        ntPath.erase(ntPath.begin(), ntPath.begin() + 4);
        return ntPath;
    }
    if (boost::starts_with(ntPath, L"\\??\\"))
    {
        ntPath.erase(ntPath.begin(), ntPath.begin() + 4);
    }
    if (boost::starts_with(ntPath, L"\\"))
    {
        ntPath.erase(ntPath.begin(), ntPath.begin() + 1);
    }
    if (boost::istarts_with(ntPath, L"globalroot\\"))
    {
        ntPath.erase(ntPath.begin(), ntPath.begin() + 11);
    }
    if (boost::istarts_with(ntPath, L"systemroot"))
    {
        ntPath.replace(ntPath.begin(), ntPath.begin() + 10, GetWindowsPath());
    }
    if (boost::istarts_with(ntPath, L"windows"))
    {
        ntPath.replace(ntPath.begin(), ntPath.begin() + 7, GetWindowsPath());
    }
    return ntPath;
}

TEST(Win32Path, NtPathDoubleQuestions)
{
    ASSERT_EQ(L"C:\\Example", NtPathToWin32Path(L"\\??\\C:\\Example"));
}

TEST(Win32Path, NtPathUncBegin)
{
    ASSERT_EQ(L"C:\\Example", NtPathToWin32Path(L"\\\\?\\C:\\Example"));
}

TEST(Win32Path, NtPathWindowsStart)
{
    ASSERT_EQ(GetCombinedPath(GetWindowsPath(), L"Hello\\World"), NtPathToWin32Path(L"\\Windows\\Hello\\World"));
}

TEST(Win32Path, NtPathSystemrootStart)
{
    ASSERT_EQ(GetCombinedPath(GetWindowsPath(), L"Hello\\World"), NtPathToWin32Path(L"\\SystemRoot\\Hello\\World"));
}

TEST(Win32Path, NtPathGlobalRootSystemRoot)
{
    ASSERT_EQ(GetCombinedPath(GetWindowsPath(), L"Hello\\World"), NtPathToWin32Path(L"\\globalroot\\SystemRoot\\Hello\\World"));
}

but I'd be strongly surprised if there's not some API, native or otherwise, which will convert these into Win32 path names. Does such an API exist?

Mink answered 14/12, 2010 at 22:52 Comment(17)
Does the shell API function PathCanonicalize do the trick? msdn.microsoft.com/en-us/library/bb773569%28v=vs.85%29.aspxAcrylyl
@Praetorian: No, PathCanonicalize accepts Win32 paths. I'm trying to get a win32 path.Mink
I don't know of any such function, and it's not always possible: NT can use paths that Win32 can't at all. Good luck anyhow…Merely
@Billy: Here's something you could try. First use NtCreateFile (msdn.microsoft.com/en-us/library/bb432380%28v=vs.85%29.aspx) to open the file, volume etc. for reading. Then use the returned HANDLE to get the full path as described here msdn.microsoft.com/en-us/library/aa366789%28v=vs.85%29.aspxAcrylyl
@ephemient: True, but NT's got to do it somewhere. After all, NtQueryDirectoryFile returns native paths, and FindFirstFile returns Win32 paths...Mink
Doesn't seem definitive to me. Of course FindFirstFile can easily construct Win32 paths; you have to give it one to start from, unlike ZwQueryDirectoryFile which takes a HANDLE instead. Of course, your problem would be solved if there were an API to return a Win32 path from a HANDLE.Merely
If you search, you can find Obtaining a File Name From a File Handle on MSDN, but it still returns an NT path and only works for objects which can be memory mapped.Merely
@Ephemient: If I can open the actual handle, then I could just call GetFileInformationByHandleEx... I'd rather avoid having to actually avoid opening the handle to the actual file if at all possible though.Mink
@Billy: Not all open file handles are created equal. If you open for QUERY_INFORMATION access level (and not GENERIC_READ or GENERIC_WRITE) then you shouldn't interfere in any way with other programs using the file.Larva
@Ben: While true, that still requires me to have access to the file, which I may not have.Mink
Rather interesting. The last time I programmed for Windows I'm pretty sure GetFileInformationByHandleEx didn't exist yet. I agree that open+query is not a pleasant way to do this (though open+mmap+query is worse), but it does seem like there's no publicly exposed method for this.Merely
@ephemient: Ah -- looks like it was added in Vista. No matter. One can call NtQueryFileInformation and ask it for FILE_NAMES_INFORMATION from a handle.Mink
@Praetorian: Put that in an answer so that we can upvote it.Mink
@Billy: Gladly, you might even have the honor bumping my rep up over 1K :-)Acrylyl
Where is GetWindowsPath() coming from ? Why not use RtlDosPathNameToNtPathName_U_WithStatus ?Turret
@NorbertBoros I can't seem to find any documentation on a function of that name.Mink
@BillyONeal I know... there are so many undocumented, but exist and used since XP to 10 :) I will post a gist or something with some of these functions and explanations.Turret
D
8

We do this in production code. As far as I know there is no API (public or private) that handles this. We just do some string comparisons with a few prefixes and it works for us.

Apparently there is a function named RtlNtPathNameToDosPathName() in ntdll.dll (introduced with XP?), but I have no idea what it does; I would guess it has more to do with stuff like \Device\Harddisk0, though.

I'm not sure there is really a need for such a function, though. Win32 passes paths (in the sense of CreateFile, etc) to NT; NT doesn't pass paths to Win32. So ntdll.dll doesn't really have a need to go from NT paths to Win32 paths. In the rare case where some NT query function returns a full path, any conversion function could be internal to the Win32 dll (e.g. not exported). I don't even know if they bother, as stuff like GetModuleFileName() will just return whatever path was used to load the image. I guess this is just a leaky abstraction.

Dory answered 15/12, 2010 at 2:29 Comment(3)
Pretty much. The only ones I've seen on any regular basis are \\??\\ and \\SystemRoot, and even then that is pretty rare (typically with low-level processes launched early in the boot sequence. Also sometimes in the registry, usually related to said processes.Dory
I'm mucking around with undocumented stuff and all those prefixes seem to be common here. I guess there's a reason they're undocumented :PMink
It is a shame that Microsoft does not provide a documented API for this. Internally Windows converts NT paths to DOS path all the day long, but we programmers cannot. If you are interested in how to use RtlNtPathNameToDosPathName() have a look here: forum.sysinternals.com/…Harvey
A
6

Here's something you could try. First use NtCreateFile to open the file, volume etc. for reading. Then use the returned HANDLE to get the full path as described here.

Acrylyl answered 15/12, 2010 at 16:15 Comment(3)
+1 :) Note that one can probably skip the file mapping step by calling NtQueryFileInformation and asking for the information class FileNameInformation. (Though I haven't tested this yet, and of course that's going into the undocumented stuff...)Mink
GetFinalPathNameByHandle might help clean this up!Smarmy
This approach worked for me. For Windows Vista and newer, there's an easier way to get the path from the handle by using this function: learn.microsoft.com/en-us/windows/win32/api/fileapi/…Shawanda
T
5

This is a bit late, but I will still post my answer since even today this is a very good question!

I will share one of my functions tested and used for converting NT to DOS path. In my case, I also had to convert from ANSI to UNICODE so this is a small bonus for you to see and understand how this can be done.

All this code can be used in User Mode, so we need to first prepare some things.

Definitions & Structures:

typedef NTSTATUS(WINAPI* pRtlAnsiStringToUnicodeString)(PUNICODE_STRING, PANSI_STRING, BOOL);

typedef struct _RTL_BUFFER {
    PUCHAR    Buffer;
    PUCHAR    StaticBuffer;
    SIZE_T    Size;
    SIZE_T    StaticSize;
    SIZE_T    ReservedForAllocatedSize; // for future doubling
    PVOID     ReservedForIMalloc; // for future pluggable growth
} RTL_BUFFER, * PRTL_BUFFER;

typedef struct _RTL_UNICODE_STRING_BUFFER {
    UNICODE_STRING String;
    RTL_BUFFER     ByteBuffer;
    UCHAR          MinimumStaticBufferForTerminalNul[sizeof(WCHAR)];
} RTL_UNICODE_STRING_BUFFER, * PRTL_UNICODE_STRING_BUFFER;

#define RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_AMBIGUOUS   (0x00000001)
#define RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_UNC         (0x00000002)
#define RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_DRIVE       (0x00000003)
#define RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_ALREADY_DOS (0x00000004)

typedef NTSTATUS(WINAPI* pRtlNtPathNameToDosPathName)(__in ULONG Flags, __inout PRTL_UNICODE_STRING_BUFFER Path, __out_opt PULONG Disposition, __inout_opt PWSTR* FilePart);

#define RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE (0x00000001)
#define RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING (0x00000002)
#define RTL_DUPSTR_ADD_NULL                          RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE
#define RTL_DUPSTR_ALLOC_NULL                        RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING

typedef NTSTATUS(WINAPI* pRtlDuplicateUnicodeString)(_In_ ULONG Flags, _In_ PUNICODE_STRING StringIn, _Out_ PUNICODE_STRING StringOut);

Importing functions:

pRtlAnsiStringToUnicodeString MyRtlAnsiStringToUnicodeString;
pRtlNtPathNameToDosPathName MyRtlNtPathNameToDosPathName;
pRtlDuplicateUnicodeString MyRtlDuplicateUnicodeString;

MyRtlAnsiStringToUnicodeString = (pRtlAnsiStringToUnicodeString)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlAnsiStringToUnicodeString");
MyRtlNtPathNameToDosPathName = (pRtlNtPathNameToDosPathName)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlNtPathNameToDosPathName");
MyRtlDuplicateUnicodeString = (pRtlDuplicateUnicodeString)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlDuplicateUnicodeString");

Helper function:

NTSTATUS NtPathNameToDosPathName(PUNICODE_STRING DosPath, PUNICODE_STRING NtPath)
{
    NTSTATUS                    Status;
    ULONG_PTR                   BufferSize;
    PWSTR                       Buffer;
    RTL_UNICODE_STRING_BUFFER   UnicodeBuffer;

    BufferSize = NtPath->MaximumLength + MAX_PATH * sizeof(WCHAR);

    Buffer = (PWSTR)_alloca(BufferSize);

    ZeroMemory(&UnicodeBuffer, sizeof(UnicodeBuffer));

    UnicodeBuffer.String = *NtPath;
    UnicodeBuffer.String.Buffer = Buffer;
    UnicodeBuffer.String.MaximumLength = (USHORT)BufferSize;
    UnicodeBuffer.ByteBuffer.Buffer = (PUCHAR)Buffer;
    UnicodeBuffer.ByteBuffer.Size = BufferSize;

    CopyMemory(Buffer, NtPath->Buffer, NtPath->Length);

    MyRtlNtPathNameToDosPathName(0, &UnicodeBuffer, NULL, NULL);

    return MyRtlDuplicateUnicodeString(RTL_DUPSTR_ADD_NULL, &UnicodeBuffer.String, DosPath);
}

Function usage:

UNICODE_STRING us;
UNICODE_STRING DosPath;
ANSI_STRING as;

as.Buffer = (char*)malloc(strlen(NT_PATH_FILE_OR_DIR) + 1);
strcpy(as.Buffer, NT_PATH_FILE_OR_DIR);
as.Length = as.MaximumLength = us.MaximumLength = us.Length = strlen(NT_PATH_FILE_OR_DIR);

MyRtlAnsiStringToUnicodeString(&us, &as, TRUE);

NtPathNameToDosPathName(&DosPath, &us);

As mentioned, in my case I needed to convert from ANSI to UNICODE and this might not apply for your case, thus you can remove it.

Same as above can be used to create custom functions and convert paths as needed.

Turret answered 24/3, 2020 at 10:7 Comment(0)
D
2

Check this out for getting the canonical pathname in Win32. It may be helpful for you:

http://pdh11.blogspot.com/2009/05/pathcanonicalize-versus-what-it-says-on.html

Deemster answered 14/12, 2010 at 22:53 Comment(1)
+1 -- this is interesting, but it's about turning a Win32 path into another kind of Win32 path. I don't have a win32 path in the first place. The PathXxx functions aren't going to work on what I have.Mink
F
2

See my answer to this question.

You'd need to first get a handle to the file at that path, and then get the Win32 path for the handle.

Felixfeliza answered 4/6, 2011 at 23:3 Comment(9)
This works -- but the problem is that you need access to the file's location in order to execute it (because you need to be able to open a handle) -- I often don't have that.Mink
@Billy: All you need is FILE_READ_ATTRIBUTES access, which you can almost always get. If you can't get it, try it on the parent directory, and then concatenate the file name at the end. (You can't convert without any handle, because it's not always a one-to-one mapping.)Felixfeliza
@Mehrdad: It's got to be possible to convert without a handle. FindFirstFile/FindNextFile/FindClose do not open handles. You can use them to list a directory structure even if you can't read any attributes on the file. This solution works, so +1, but it really doesn't help me :(Mink
@Billy: Actually, FindFirstFile does open a handle to the directory! What makes you think it doesn't? :)Felixfeliza
@Mehrdad: The directory, yes. Not the files inside the directory.Mink
@Billy: But like I mentioned in my comment, can't you just try my method on the parent directory, and concatenate the file names at the end? Seems like that would work.Felixfeliza
@Mehrdad: That just turns into another maze of string manipulations. That won't work out of the box, for example, with \??\C:\Windows\... Stripping the last file leads to C:\Windows, tacking the .. back on leads to c:\Windows\.. . I guess you could re-canonicalize afterward... either way, the current series of string manipulations hasn't failed me yet.Mink
@Billy: Huh, okay... if you run into problems with your current method then I guess give this a try. :)Felixfeliza
GetFinalPathNameByHandle is the best way to go, by far. You don't need to know whether the target is a file or directory, just use CreateFileWto get a handle for your path, passing 0 (zero) for dwDesiredAccess (FILE_ACCESS) and FILE_FLAG_BACKUP_SEMANTICS (only). These tell CreateFileW that you won't actually be opening the file, which bypasses the security problems some people mentioned. You will still be able to use GetFinalPathNameByHandle to resolve a full, minimal path to the target. I do this heavily and successfully to resolve cross-volume reparse points all day long.Diametral
H
0

I wrote a function that converts different types of NT device names (filenames, COM ports, network paths, etc.) into a DOS path.

There are two functions. One converts a handle into an NT path and the other one converts this NT path into a DOS path.

Have a look here: How to get name associated with open HANDLE

// "\Device\HarddiskVolume3"                                (Harddisk Drive)
// "\Device\HarddiskVolume3\Temp"                           (Harddisk Directory)
// "\Device\HarddiskVolume3\Temp\transparent.jpeg"          (Harddisk File)
// "\Device\Harddisk1\DP(1)0-0+6\foto.jpg"                  (USB stick)
// "\Device\TrueCryptVolumeP\Data\Passwords.txt"            (Truecrypt Volume)
// "\Device\Floppy0\Autoexec.bat"                           (Floppy disk)
// "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB"                   (DVD drive)
// "\Device\Serial1"                                        (real COM port)
// "\Device\USBSER000"                                      (virtual COM port)
// "\Device\Mup\ComputerName\C$\Boot.ini"                   (network drive share,  Windows 7)
// "\Device\LanmanRedirector\ComputerName\C$\Boot.ini"      (network drive share,  Windwos XP)
// "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" (network folder share, Windwos XP)
// "\Device\Afd"                                            (internet socket)
// "\Device\Console000F"                                    (unique name for any Console handle)
// "\Device\NamedPipe\Pipename"                             (named pipe)
// "\BaseNamedObjects\Objectname"                           (named mutex, named event, named semaphore)
// "\REGISTRY\MACHINE\SOFTWARE\Classes\.txt"                (HKEY_CLASSES_ROOT\.txt)
Harvey answered 11/8, 2015 at 3:26 Comment(0)
S
0

Ran across this thread as I needed the same thing. I was using the EnumDeviceDrivers and GetDeviceDriverFileName functions, and the resulting filenames were in NT format. I then wanted to call GetFileVersionInfo on the result, which did not accept that path format.

I first tried using the undocumented RtlNtPathNameToDosPathName function, but it didn't work for me - the path had a \\SystemRoot prefix and said function didn't seem to do anything about that. What ultimately worked was the NtCreateFile approach suggested in https://mcmap.net/q/918890/-how-can-i-convert-a-native-nt-pathname-into-a-win32-path-name. A simpler way to get the path from the handle is using GetFinalPathNameByHandle, which is available as of Windows Vista.

Here's my full code:

#include <windows.h>
#include <winternl.h>


std::wstring NtPathToDosPath(const std::wstring& Path)
{
  UNICODE_STRING PathString;
  RtlInitUnicodeString(&PathString, Path.data());

  OBJECT_ATTRIBUTES ObjectAttributes;
  InitializeObjectAttributes(&ObjectAttributes, &PathString, OBJ_CASE_INSENSITIVE, nullptr, nullptr);

  HANDLE FileHandle;
  IO_STATUS_BLOCK   IoStatus;

  const auto Status = NtCreateFile(&FileHandle,
                             GENERIC_READ | SYNCHRONIZE,
                             &ObjectAttributes,
                             &IoStatus,
                             nullptr,
                             FILE_ATTRIBUTE_NORMAL,
                             FILE_SHARE_READ,
                             FILE_OPEN,
                             FILE_SYNCHRONOUS_IO_NONALERT | FILE_COMPLETE_IF_OPLOCKED,
                             nullptr,
                             0);

  if (!NT_SUCCESS(Status))
  {
    // Report error
    return {};
  }

  const auto NeededSize = GetFinalPathNameByHandleW(FileHandle, nullptr, 0, 0);

  std::wstring OutPath;
  OutPath.resize(NeededSize);

  if (!GetFinalPathNameByHandleW(FileHandle, &(OutPath[0]), OutPath.size(), 0))
  {
    // Report error
    return {};
  }

  return OutPath;
}
Shawanda answered 6/10, 2023 at 10:23 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.