Why does os.path.realpath convert c:\windows\system32 into c:/windows/syswow64?

Viewed 40

If we take a look to https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve source code we can see the routine is calling behind the curtains https://docs.python.org/3/library/os.path.html#os.path.realpath

I'm trying to really understand how os.path.realpath works in certain directories such as c:\windows\system32, ie:

>>> from pathlib import Path                                                    
>>> Path("c:/windows/system32")                                                 
WindowsPath('c:/windows/system32')                                              
>>> Path("c:/windows/system32").resolve()                                       
WindowsPath('C:/Windows/SysWOW64')                                              
>>> Path("c:/windows/system32").resolve(strict=True)                            
WindowsPath('C:/Windows/SysWOW64')                                              
>>> Path("c:/windows/system32").resolve()==Path("c:/windows/system32")          
False          

Or directories such as c:/windows/system32/openssh where you can get "unexpected results" such as below:

enter image description here

>>> list(Path("c:/windows/system32/openssh").resolve().glob("*"))
[]
>>> list(Path("c:/windows/system32/openssh").glob("*"))
[]

or

>>> os.listdir(r"C:\Windows\System32\OpenSSH")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [WinError 3] The system cannot find the path specified: 'C:\\Windows\\System32\\OpenSSH'

If you do dir /a you'll get

24/09/2022  10:03    <DIR>          System32

So you can see it's neither a SYMLINKD nor JUNCTION.

Could you explain how os.path.realpath works in these cases? Why can't i list/glob the content of that c:\windows\system32\openssh folder?

References: ntpath.py

2 Answers

On windows, os.path.realpath is using GetFinalPathNameByHandleW behind the curtains, for more info you can check here.

If we make a simple experiment to see how that routine works:

#include <windows.h>
#include <iostream>

#define MAXPATHLEN 1024

void os__getfinalpathname_impl(const std::wstring &path) {
    std::wcout << L"analizing: " << path << std::endl;

    HANDLE hFile;
    wchar_t buf[MAXPATHLEN];
    int result_length;

    hFile = CreateFileW(path.c_str(), 0, 0, NULL, OPEN_EXISTING,
                        FILE_FLAG_BACKUP_SEMANTICS, NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        std::wcout << L"CreateFileW:" << path << std::endl;
    }

    result_length =
        GetFinalPathNameByHandleW(hFile, buf, MAXPATHLEN, VOLUME_NAME_DOS);

    if (!result_length) {
        std::wcout << L"GetFinalPathNameByHandleW" << std::endl;
    }

    std::wcout << L"result: " << buf << std::endl;
}

void main(int argc, char *argv[]) {
    std::wcout << L"test1" << std::endl;
    os__getfinalpathname_impl(L"c:/windows/system32");
    std::wcout << L"test2" << std::endl;
    os__getfinalpathname_impl(L"c:\\windows\\system32");
}

If we compile it for a 32-bit target we'll get:

test1
analizing: c:/windows/system32
result: \\?\C:\Windows\SysWOW64
test2
analizing: c:\windows\system32
result: \\?\C:\Windows\SysWOW64

If we compile it for a 64-bit target we'll get:

test1
analizing: c:/windows/system32
result: \\?\C:\Windows\System32
test2
analizing: c:\windows\system32
result: \\?\C:\Windows\System32

Now, if we want to confirm the above and checking the differences in a python context, we can do that easily by just spawning a couple oneliners:

python\3.10.6-amd64\python.exe -c "from pathlib import Path; print(Path('c:/windows/system32').resolve())"
C:\Windows\System32

python\3.10.1\python.exe -c "from pathlib import Path; print(Path('c:/windows/system32').resolve())"
C:\Windows\SysWOW64

Which makes total sense accordingly what's described in the below link https://learn.microsoft.com/en-us/windows/win32/winprog64/file-system-redirector

Can't yet comment so I write an answer, event that I only have a hint for you.

The WoW64 Subsystems is a complicated thing but can sometimes feel like a symlink. It kinda handles 32 and 64 bit compaitbility and creates "dynamic" paths depending on what you are doing. It's a bit like if you run windows in german it will show you "Bilder" instead of "Pictures" but in the background it never changes.

You'll find more (basic) information about it on Wikipedia WoW64 on Wikipedia

Another example would be the built-in variable %COMSPEC% that would be

  • c:\windows\system32\cmd.exe on 64bit
  • C:\Windows\SysWOW64\cmd.exe on 32bit WoW

Sometimes you see teh SysWOW64 showing through, but most often all you see is system32

Long story short: You will need to dig deeper into the WoW64 topic.

Related