In CopyFileEx, and DeleteFile functions, by default, the file name and path are limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, need prepend "\\?\" to the path.
-- v3: kernelbase: Limit the maximum path length for DeleteFile. kernelbase: Fix DeleteFileA doesn't support long path. kernelbase: Limit the maximum path length for filesystem. user32: Load the value of longPathAware from manifest. kernel32/tests: Add tests for maximum path length limitation.
From: yaoyongjie yaoyongjie@uniontech.com
In CopyFileEx, and DeleteFile functions, by default, the file name and path are limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, need prepend "\\?\" to the path.
Starting in Windows 10, version 1607, MAX_PATH limitations have been removed from many common Win32 file and directory functions. However, your app must opt-in to the new behavior.
To enable the new long path behavior per application, two conditions must be met. A registry value must be set, and the application manifest must include the longPathAware element. --- dlls/kernel32/tests/file.c | 258 +++++++++++++++++++++++++ dlls/kernel32/tests/wine_test.manifest | 9 +- 2 files changed, 265 insertions(+), 2 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 38c68297097..046b0cedaea 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -34,6 +34,7 @@ #include "winternl.h" #include "winnls.h" #include "fileapi.h" +#include "winreg.h"
static HANDLE (WINAPI *pFindFirstFileExA)(LPCSTR,FINDEX_INFO_LEVELS,LPVOID,FINDEX_SEARCH_OPS,LPVOID,DWORD); static BOOL (WINAPI *pReplaceFileW)(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID); @@ -904,12 +905,92 @@ static void test_CopyFileA(void) ok(ret, "DeleteFileA: error %ld\n", GetLastError()); }
+ +typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); +/* Windows 10, version 1607, building number 14393, Redstone, August 2, 2016 */ +static BOOL is_win10_1607_or_later() +{ + static RTL_OSVERSIONINFOW rovi = { 0 }; + if (rovi.dwOSVersionInfoSize == 0) + { + rovi.dwOSVersionInfoSize = sizeof(rovi); + HMODULE hMod = GetModuleHandleW(L"ntdll.dll"); + if (hMod) { + RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)GetProcAddress(hMod, "RtlGetVersion"); + if (fxPtr) { + if (S_OK == fxPtr(&rovi)) { + trace("windows version: win %ld, build %ld\n", rovi.dwMajorVersion, rovi.dwBuildNumber); + } + } + } + } + + return rovi.dwMajorVersion >= 10 && rovi.dwBuildNumber >= 14393; +} + +static BOOL is_reg_enable_long_path() +{ + static DWORD LongPathEnabled = -1; + if (LongPathEnabled == -1) + { + DWORD ret; + HKEY hkey = (HKEY)0xdeadbeef; + + ret = RegOpenKeyExA( HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\FileSystem", 0, KEY_READ, &hkey); + if (ret == ERROR_SUCCESS) + { + DWORD type, size = sizeof(LongPathEnabled); + ret = RegQueryValueExA(hkey, "LongPathsEnabled", NULL, &type, (LPBYTE)&LongPathEnabled, &size); + RegCloseKey(hkey); + } + else + { + LongPathEnabled = 0; + } + trace("in registry LongPathsEnabled: %ld\n", LongPathEnabled); + } + return LongPathEnabled == 1; +} + +static BOOL is_app_enabled_long_path() +{ + static WCHAR buffer[10] = { 0 }; + static const WCHAR trueW[] = {'t','r','u','e',0}; + + if (buffer[0] == 0) + { + BOOL ret; + size_t size = 0xdead; + static const WCHAR longPathAwareW[] = {'l','o','n','g','P','a','t','h','A','w','a','r','e',0}; + static const WCHAR namespace2016W[] = {'h','t','t','p',':','/','/','s','c','h','e','m','a','s','.','m','i','c','r','o','s','o','f','t','.','c','o','m','/','S','M','I','/','2','0','1','6','/','W','i','n','d','o','w','s','S','e','t','t','i','n','g','s',0}; + memset( buffer, 0xcc, sizeof(buffer) ); + ret = QueryActCtxSettingsW( 0, NULL, namespace2016W, longPathAwareW, buffer, lstrlenW(trueW) + 1, &size ); + if (ret && size == lstrlenW(trueW) + 1) + { + trace("in app manifest LongPathAware: %s\n", debugstr_w(buffer)); + } + } + + return lstrcmpiW(buffer, trueW) == 0; +} + +static BOOL is_enabled_long_path() +{ + return is_win10_1607_or_later() && is_reg_enable_long_path() && is_app_enabled_long_path(); +} + static void test_CopyFileW(void) { WCHAR temp_path[MAX_PATH]; WCHAR source[MAX_PATH], dest[MAX_PATH]; static const WCHAR prefix[] = {'p','f','x',0}; DWORD ret; + /* long file name, 247 charactors */ + const WCHAR *long_name_1 = L"a2345678lsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfie.txt"; + const WCHAR *long_name_2 = L"b2345678lsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfie.txt"; + /* long file path is %TEMP%/%long_name% */ + WCHAR long_path_1[MAX_PATH * 2] = { 0 }; + WCHAR long_path_2[MAX_PATH * 2] = { 0 };
ret = GetTempPathW(MAX_PATH, temp_path); if (ret == 0 && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) @@ -942,6 +1023,92 @@ static void test_CopyFileW(void) ok(GetLastError() == ERROR_SUCCESS || broken(GetLastError() == ERROR_INVALID_PARAMETER) /* some win8 machines */, "Unexpected error %lu.\n", GetLastError());
+ /* test long file path, the length of the long_dest is larger than MAX_PATH. */ + if (is_enabled_long_path()) + { + wcscpy(long_path_1, temp_path); + wcscat(long_path_1, long_name_1); + SetLastError(0xdeadbeef); + ret = CopyFileExW(source, long_path_1, NULL, NULL, NULL, 0); + ok(ret, "Expected CopyFileExW successed, but got %ld, copy %s -> %s\n", GetLastError(), wine_dbgstr_w(source), wine_dbgstr_w(long_path_1)); + wcscpy(long_path_2, temp_path); + wcscat(long_path_2, long_name_2); + SetLastError(0xdeadbeef); + ret = CopyFileExW(long_path_1, long_path_2, NULL, NULL, NULL, 0); + ok(ret, "Expected CopyFileExW successed, but got %ld, copy %s -> %s\n", GetLastError(), wine_dbgstr_w(long_path_1), wine_dbgstr_w(long_path_2)); + ret = DeleteFileW(long_path_1); + ok(ret, "Unexpected DeleteFileW successed\n"); + ret = DeleteFileW(long_path_2); + ok(ret, "Unexpected DeleteFileW successed\n"); + } + else + { + wcscpy(long_path_1, temp_path); + wcscat(long_path_1, long_name_1); + SetLastError(0xdeadbeef); + ret = CopyFileExW(source, long_path_1, NULL, NULL, NULL, 0); + todo_wine ok(!ret && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExW failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), wine_dbgstr_w(source), wine_dbgstr_w(long_path_1)); + wcscpy(long_path_2, temp_path); + wcscat(long_path_2, long_name_2); + SetLastError(0xdeadbeef); + ret = CopyFileExW(long_path_1, long_path_2, NULL, NULL, NULL, 0); + todo_wine ok(!ret && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExW failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), wine_dbgstr_w(long_path_1), wine_dbgstr_w(long_path_2)); + ret = DeleteFileW(long_path_1); + todo_wine ok(!ret, "Unexpected DeleteFileW successed\n"); + ret = DeleteFileW(long_path_2); + todo_wine ok(!ret, "Unexpected DeleteFileW successed\n"); + } + + /* test long file name prepend "\?" */ + wcscpy(long_path_1, L"\\?\"); + wcscat(long_path_1, temp_path); + wcscat(long_path_1, long_name_1); + SetLastError(0xdeadbeef); + ret = CopyFileExW(source, long_path_1, NULL, NULL, NULL, 0); + ok(ret, "CopyFileExW failed, got %ld, copy %s -> %s failed\n", GetLastError(), wine_dbgstr_w(source), wine_dbgstr_w(long_path_1)); + wcscpy(long_path_2, L"\\?\"); + wcscat(long_path_2, temp_path); + wcscat(long_path_2, long_name_2); + SetLastError(0xdeadbeef); + ret = CopyFileExW(long_path_1, long_path_2, NULL, NULL, NULL, 0); + ok(ret, "CopyFileExW failed, got %ld, copy %s -> %s failed\n", GetLastError(), wine_dbgstr_w(long_path_1), wine_dbgstr_w(long_path_2)); + + if (is_enabled_long_path()) + { + wcscpy(long_path_1, temp_path); + wcscat(long_path_1, long_name_1); + ret = DeleteFileW(long_path_1); + todo_wine ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + wcscpy(long_path_2, temp_path); + wcscat(long_path_2, long_name_2); + ret = DeleteFileW(long_path_2); + todo_wine ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + } + else + { + wcscpy(long_path_1, temp_path); + wcscat(long_path_1, long_name_1); + ret = DeleteFileW(long_path_1); + todo_wine ok(!ret, "Unexpected DeleteFileW successed, %s\n", wine_dbgstr_w(long_path_1)); + wcscpy(long_path_2, temp_path); + wcscat(long_path_2, long_name_2); + ret = DeleteFileW(long_path_2); + todo_wine ok(!ret, "Unexpected DeleteFileW successed, %s\n", wine_dbgstr_w(long_path_1)); + + wcscpy(long_path_1, L"\\?\"); + wcscat(long_path_1, temp_path); + wcscat(long_path_1, long_name_1); + SetLastError(0xdeadbeef); + ret = DeleteFileW(long_path_1); + todo_wine ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + wcscpy(long_path_2, L"\\?\"); + wcscat(long_path_2, temp_path); + wcscat(long_path_2, long_name_2); + SetLastError(0xdeadbeef); + ret = DeleteFileW(long_path_2); + todo_wine ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + } + ret = DeleteFileW(source); ok(ret, "DeleteFileW: error %ld\n", GetLastError()); ret = DeleteFileW(dest); @@ -1194,6 +1361,12 @@ static void test_CopyFileEx(void) HANDLE hfile; DWORD ret; BOOL retok; + /* long file name, 247 charactors */ + const char *long_name_1 = "a2345678lsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfie.txt"; + const char *long_name_2 = "b2345678lsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfielfelfjellsfalfielsfleflsfie.txt"; + /* long file path is %TEMP%/%long_name% */ + char long_path_1[MAX_PATH * 2] = { 0 }; + char long_path_2[MAX_PATH * 2] = { 0 };
ret = GetTempPathA(MAX_PATH, temp_path); ok(ret != 0, "GetTempPathA error %ld\n", GetLastError()); @@ -1235,6 +1408,91 @@ static void test_CopyFileEx(void) ok(!retok, "CopyFileExA unexpectedly succeeded\n"); ok(GetLastError() == ERROR_PATH_NOT_FOUND, "expected ERROR_PATH_NOT_FOUND, got %ld\n", GetLastError());
+ /* test long file path, the length of the long_dest is larger than MAX_PATH. */ + if (is_enabled_long_path()) + { + strcpy(long_path_1, temp_path); + strcat(long_path_1, long_name_1); + SetLastError(0xdeadbeef); + retok = CopyFileExA(source, long_path_1, NULL, NULL, NULL, 0); + ok(retok, "Expected CopyFileExA succeeded, but got %ld, copy %s -> %s\n", GetLastError(), source, long_path_1); + strcpy(long_path_2, temp_path); + strcat(long_path_2, long_name_2); + SetLastError(0xdeadbeef); + retok = CopyFileExA(long_path_1, long_path_2, NULL, NULL, NULL, 0); + ok(retok, "Expected CopyFileExA succeeded, but got %ld, copy %s -> %s\n", GetLastError(), long_path_1, long_path_2); + retok = DeleteFileA(long_path_1); + todo_wine ok(retok, "DeleteFileA failed: %ld, %s\n", GetLastError(), long_path_1); + retok = DeleteFileA(long_path_2); + todo_wine ok(retok, "DeleteFileA failed: %ld, %s\n", GetLastError(), long_path_2); + } + else + { + strcpy(long_path_1, temp_path); + strcat(long_path_1, long_name_1); + SetLastError(0xdeadbeef); + retok = CopyFileExA(source, long_path_1, NULL, NULL, NULL, 0); + todo_wine ok(!retok && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExA failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), source, long_path_1); + strcpy(long_path_2, temp_path); + strcat(long_path_2, long_name_2); + SetLastError(0xdeadbeef); + retok = CopyFileExA(long_path_1, long_path_2, NULL, NULL, NULL, 0); + todo_wine ok(!retok && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExA failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), long_path_1, long_path_2); + retok = DeleteFileA(long_path_1); + todo_wine ok(!retok, "Unexpected DeleteFileA successed\n"); + retok = DeleteFileA(long_path_2); + todo_wine ok(!retok, "Unexpected DeleteFileA successed\n"); + } + + /* test long file name prepend "\?" */ + strcpy(long_path_1, "\\?\"); + strcat(long_path_1, temp_path); + strcat(long_path_1, long_name_1); + SetLastError(0xdeadbeef); + retok = CopyFileExA(source, long_path_1, NULL, NULL, NULL, 0); + ok(retok, "CopyFileExA failed, got %ld, copy %s -> %s failed\n", GetLastError(), source, long_path_1); + strcpy(long_path_2, "\\?\"); + strcat(long_path_2, temp_path); + strcat(long_path_2, long_name_2); + SetLastError(0xdeadbeef); + retok = CopyFileExA(long_path_1, long_path_2, NULL, NULL, NULL, 0); + ok(retok, "CopyFileExA failed, got %ld, copy %s -> %s failed\n", GetLastError(), long_path_1, long_path_2); + if (is_enabled_long_path()) + { + strcpy(long_path_1, temp_path); + strcat(long_path_1, long_name_1); + ret = DeleteFileA(long_path_1); + todo_wine ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); + strcpy(long_path_2, temp_path); + strcat(long_path_2, long_name_2); + ret = DeleteFileA(long_path_2); + todo_wine ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); + } + else + { + strcpy(long_path_1, temp_path); + strcat(long_path_1, long_name_1); + ret = DeleteFileA(long_path_1); + todo_wine ok(!ret, "Unexpected DeleteFileA successed, %s\n", long_path_1); + strcpy(long_path_2, temp_path); + strcat(long_path_2, long_name_2); + ret = DeleteFileA(long_path_2); + todo_wine ok(!ret, "Unexpected DeleteFileA successed, %s\n", long_path_2); + + strcpy(long_path_1, "\\?\"); + strcat(long_path_1, temp_path); + strcat(long_path_1, long_name_1); + SetLastError(0xdeadbeef); + ret = DeleteFileA(long_path_1); + todo_wine ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); + strcpy(long_path_2, "\\?\"); + strcat(long_path_2, temp_path); + strcat(long_path_2, long_name_2); + SetLastError(0xdeadbeef); + ret = DeleteFileA(long_path_2); + todo_wine ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); + } + ret = DeleteFileA(source); ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); ret = DeleteFileA(dest); diff --git a/dlls/kernel32/tests/wine_test.manifest b/dlls/kernel32/tests/wine_test.manifest index de77de3e201..124fa6713ac 100644 --- a/dlls/kernel32/tests/wine_test.manifest +++ b/dlls/kernel32/tests/wine_test.manifest @@ -1,4 +1,9 @@ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> - <assemblyIdentity version="1.0.0.0" name="Wine.Test"/> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"> + <assemblyIdentity type="win32" version="1.0.0.0" name="Wine.Test"/> + asmv3:application + <asmv3:windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings%22%3E + ws2:longPathAwaretrue</ws2:longPathAware> + </asmv3:windowsSettings> + </asmv3:application> </assembly>
From: yaoyongjie yaoyongjie@uniontech.com
--- dlls/user32/sysparams.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/dlls/user32/sysparams.c b/dlls/user32/sysparams.c index a7ce2e54d35..0bc6c9560b0 100644 --- a/dlls/user32/sysparams.c +++ b/dlls/user32/sysparams.c @@ -210,6 +210,15 @@ void SYSPARAMS_Init(void) else SetProcessDpiAwarenessContext( DPI_AWARENESS_CONTEXT_UNAWARE ); } + else if (QueryActCtxSettingsW( 0, NULL, L"http://schemas.microsoft.com/SMI/2016/WindowsSettings", + L"longPathAware", buffer, ARRAY_SIZE(buffer), NULL )) + { + TRACE( "got longPathAware=%s\n", debugstr_w(buffer) ); + if (!wcsicmp( buffer, L"true" )) + { + RtlGetCurrentPeb()->IsLongPathAwareProcess = 1; + } + } }
static BOOL update_desktop_wallpaper(void)
From: yaoyongjie yaoyongjie@uniontech.com
In CopyFileEx, by default, the file name and path are limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, need prepend "\\?\" to the path. --- dlls/kernel32/tests/file.c | 8 ++--- dlls/kernelbase/file.c | 68 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 046b0cedaea..ded3cde2624 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -1047,12 +1047,12 @@ static void test_CopyFileW(void) wcscat(long_path_1, long_name_1); SetLastError(0xdeadbeef); ret = CopyFileExW(source, long_path_1, NULL, NULL, NULL, 0); - todo_wine ok(!ret && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExW failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), wine_dbgstr_w(source), wine_dbgstr_w(long_path_1)); + ok(!ret && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExW failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), wine_dbgstr_w(source), wine_dbgstr_w(long_path_1)); wcscpy(long_path_2, temp_path); wcscat(long_path_2, long_name_2); SetLastError(0xdeadbeef); ret = CopyFileExW(long_path_1, long_path_2, NULL, NULL, NULL, 0); - todo_wine ok(!ret && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExW failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), wine_dbgstr_w(long_path_1), wine_dbgstr_w(long_path_2)); + ok(!ret && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExW failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), wine_dbgstr_w(long_path_1), wine_dbgstr_w(long_path_2)); ret = DeleteFileW(long_path_1); todo_wine ok(!ret, "Unexpected DeleteFileW successed\n"); ret = DeleteFileW(long_path_2); @@ -1432,12 +1432,12 @@ static void test_CopyFileEx(void) strcat(long_path_1, long_name_1); SetLastError(0xdeadbeef); retok = CopyFileExA(source, long_path_1, NULL, NULL, NULL, 0); - todo_wine ok(!retok && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExA failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), source, long_path_1); + ok(!retok && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExA failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), source, long_path_1); strcpy(long_path_2, temp_path); strcat(long_path_2, long_name_2); SetLastError(0xdeadbeef); retok = CopyFileExA(long_path_1, long_path_2, NULL, NULL, NULL, 0); - todo_wine ok(!retok && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExA failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), long_path_1, long_path_2); + ok(!retok && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExA failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), long_path_1, long_path_2); retok = DeleteFileA(long_path_1); todo_wine ok(!retok, "Unexpected DeleteFileA successed\n"); retok = DeleteFileA(long_path_2); diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index a2c34a55b40..272ecbe5f1b 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -69,6 +69,62 @@ const WCHAR system_dir[] = L"C:\windows\system32";
static BOOL oem_file_apis;
+typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); +/* Windows 10, version 1607, building number 14393, Redstone, August 2, 2016 */ +static BOOL is_win10_1607_or_later() +{ + static RTL_OSVERSIONINFOW rovi = { 0 }; + if (rovi.dwOSVersionInfoSize == 0) + { + rovi.dwOSVersionInfoSize = sizeof(rovi); + if (RtlGetVersion(&rovi) == ERROR_SUCCESS) + { + TRACE("windows version: win %ld, build %ld\n", rovi.dwMajorVersion, rovi.dwBuildNumber); + } + else + { + ERR("RtlGetVersion failed\n"); + rovi.dwMajorVersion = 0; + rovi.dwBuildNumber = 0; + } + } + + return rovi.dwMajorVersion >= 10 && rovi.dwBuildNumber >= 14393; +} + +static BOOL is_longpath_enabled_in_reg() +{ + static DWORD LongPathEnabled = -1; + if (LongPathEnabled == -1) + { + DWORD ret; + HKEY hkey = (HKEY)0xdeadbeef; + + ret = RegOpenKeyExA( HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\FileSystem", 0, KEY_READ, &hkey); + if (ret == ERROR_SUCCESS) + { + DWORD type, size = sizeof(LongPathEnabled); + ret = RegQueryValueExA(hkey, "LongPathsEnabled", NULL, &type, (LPBYTE)&LongPathEnabled, &size); + RegCloseKey(hkey); + } + else + { + LongPathEnabled = 0; + } + TRACE("in registry LongPathsEnabled: %ld\n", LongPathEnabled); + } + return LongPathEnabled == 1; +} + +static BOOL is_longpath_enabled_in_manifest() +{ + return RtlGetCurrentPeb()->IsLongPathAwareProcess != 0; +} + +static BOOL is_longpath_enabled() +{ + return is_win10_1607_or_later() && is_longpath_enabled_in_reg() && is_longpath_enabled_in_manifest(); +}
static void WINAPI read_write_apc( void *apc_user, PIO_STATUS_BLOCK io, ULONG reserved ) { @@ -512,6 +568,18 @@ static BOOL copy_file( const WCHAR *source, const WCHAR *dest, COPYFILE2_EXTENDE SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } + + if (!is_longpath_enabled() && wcsncmp(source, L"\\?\", 4) && wcslen(source) >= MAX_PATH ) + { + SetLastError( ERROR_PATH_NOT_FOUND ); + return FALSE; + } + if (!is_longpath_enabled() && wcsncmp(dest, L"\\?\", 4) && wcslen(dest) >= MAX_PATH ) + { + SetLastError( ERROR_PATH_NOT_FOUND ); + return FALSE; + } + if (!(buffer = HeapAlloc( GetProcessHeap(), 0, buffer_size ))) { SetLastError( ERROR_NOT_ENOUGH_MEMORY );
From: yaoyongjie yaoyongjie@uniontech.com
If alloc is FALSE, file_name_AtoW will uses the TEB static buffer, but the buffer size is MAX_PATH+1 . If DeleteFileA is to support long paths prefixed with "\\?\", alloc must be set to TRUE. --- dlls/kernel32/tests/file.c | 17 +++++++++-------- dlls/kernelbase/file.c | 7 +++++-- 2 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index ded3cde2624..da4fd8d4ec8 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -1422,9 +1422,9 @@ static void test_CopyFileEx(void) retok = CopyFileExA(long_path_1, long_path_2, NULL, NULL, NULL, 0); ok(retok, "Expected CopyFileExA succeeded, but got %ld, copy %s -> %s\n", GetLastError(), long_path_1, long_path_2); retok = DeleteFileA(long_path_1); - todo_wine ok(retok, "DeleteFileA failed: %ld, %s\n", GetLastError(), long_path_1); + ok(retok, "DeleteFileA failed: %ld, %s\n", GetLastError(), long_path_1); retok = DeleteFileA(long_path_2); - todo_wine ok(retok, "DeleteFileA failed: %ld, %s\n", GetLastError(), long_path_2); + ok(retok, "DeleteFileA failed: %ld, %s\n", GetLastError(), long_path_2); } else { @@ -1439,9 +1439,9 @@ static void test_CopyFileEx(void) retok = CopyFileExA(long_path_1, long_path_2, NULL, NULL, NULL, 0); ok(!retok && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExA failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), long_path_1, long_path_2); retok = DeleteFileA(long_path_1); - todo_wine ok(!retok, "Unexpected DeleteFileA successed\n"); + ok(!retok, "Unexpected DeleteFileA successed\n"); retok = DeleteFileA(long_path_2); - todo_wine ok(!retok, "Unexpected DeleteFileA successed\n"); + ok(!retok, "Unexpected DeleteFileA successed\n"); }
/* test long file name prepend "\?" */ @@ -1457,16 +1457,17 @@ static void test_CopyFileEx(void) SetLastError(0xdeadbeef); retok = CopyFileExA(long_path_1, long_path_2, NULL, NULL, NULL, 0); ok(retok, "CopyFileExA failed, got %ld, copy %s -> %s failed\n", GetLastError(), long_path_1, long_path_2); + if (is_enabled_long_path()) { strcpy(long_path_1, temp_path); strcat(long_path_1, long_name_1); ret = DeleteFileA(long_path_1); - todo_wine ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); + ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); strcpy(long_path_2, temp_path); strcat(long_path_2, long_name_2); ret = DeleteFileA(long_path_2); - todo_wine ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); + ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); } else { @@ -1484,13 +1485,13 @@ static void test_CopyFileEx(void) strcat(long_path_1, long_name_1); SetLastError(0xdeadbeef); ret = DeleteFileA(long_path_1); - todo_wine ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); + ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); strcpy(long_path_2, "\\?\"); strcat(long_path_2, temp_path); strcat(long_path_2, long_name_2); SetLastError(0xdeadbeef); ret = DeleteFileA(long_path_2); - todo_wine ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); + ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); }
ret = DeleteFileA(source); diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index 272ecbe5f1b..6db871c9483 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -1059,9 +1059,12 @@ BOOLEAN WINAPI /* DECLSPEC_HOTPATCH */ CreateSymbolicLinkW( LPCWSTR link, LPCWST BOOL WINAPI DECLSPEC_HOTPATCH DeleteFileA( LPCSTR path ) { WCHAR *pathW; + BOOL res;
- if (!(pathW = file_name_AtoW( path, FALSE ))) return FALSE; - return DeleteFileW( pathW ); + if (!(pathW = file_name_AtoW( path, TRUE ))) return FALSE; + res = DeleteFileW( pathW ); + HeapFree( GetProcessHeap(), 0, pathW ); + return res; }
From: yaoyongjie yaoyongjie@uniontech.com
In DeleteFile functions, by default, the file name and path are limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, need prepend "\\?\" to the path. --- dlls/kernel32/tests/file.c | 20 ++++++++++---------- dlls/kernelbase/file.c | 6 ++++++ 2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index da4fd8d4ec8..797834a63ab 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -1054,9 +1054,9 @@ static void test_CopyFileW(void) ret = CopyFileExW(long_path_1, long_path_2, NULL, NULL, NULL, 0); ok(!ret && GetLastError() == ERROR_PATH_NOT_FOUND, "Expected CopyFileExW failed with ERROR_PATH_NOT_FOUND, but got %ld, copy %s -> %s\n", GetLastError(), wine_dbgstr_w(long_path_1), wine_dbgstr_w(long_path_2)); ret = DeleteFileW(long_path_1); - todo_wine ok(!ret, "Unexpected DeleteFileW successed\n"); + ok(!ret, "Unexpected DeleteFileW successed\n"); ret = DeleteFileW(long_path_2); - todo_wine ok(!ret, "Unexpected DeleteFileW successed\n"); + ok(!ret, "Unexpected DeleteFileW successed\n"); }
/* test long file name prepend "\?" */ @@ -1078,35 +1078,35 @@ static void test_CopyFileW(void) wcscpy(long_path_1, temp_path); wcscat(long_path_1, long_name_1); ret = DeleteFileW(long_path_1); - todo_wine ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + ok(ret, "DeleteFileW: error %ld\n", GetLastError()); wcscpy(long_path_2, temp_path); wcscat(long_path_2, long_name_2); ret = DeleteFileW(long_path_2); - todo_wine ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + ok(ret, "DeleteFileW: error %ld\n", GetLastError()); } else { wcscpy(long_path_1, temp_path); wcscat(long_path_1, long_name_1); ret = DeleteFileW(long_path_1); - todo_wine ok(!ret, "Unexpected DeleteFileW successed, %s\n", wine_dbgstr_w(long_path_1)); + ok(!ret, "Unexpected DeleteFileW successed, %s\n", wine_dbgstr_w(long_path_1)); wcscpy(long_path_2, temp_path); wcscat(long_path_2, long_name_2); ret = DeleteFileW(long_path_2); - todo_wine ok(!ret, "Unexpected DeleteFileW successed, %s\n", wine_dbgstr_w(long_path_1)); + ok(!ret, "Unexpected DeleteFileW successed, %s\n", wine_dbgstr_w(long_path_1));
wcscpy(long_path_1, L"\\?\"); wcscat(long_path_1, temp_path); wcscat(long_path_1, long_name_1); SetLastError(0xdeadbeef); ret = DeleteFileW(long_path_1); - todo_wine ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + ok(ret, "DeleteFileW: error %ld\n", GetLastError()); wcscpy(long_path_2, L"\\?\"); wcscat(long_path_2, temp_path); wcscat(long_path_2, long_name_2); SetLastError(0xdeadbeef); ret = DeleteFileW(long_path_2); - todo_wine ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + ok(ret, "DeleteFileW: error %ld\n", GetLastError()); }
ret = DeleteFileW(source); @@ -1474,11 +1474,11 @@ static void test_CopyFileEx(void) strcpy(long_path_1, temp_path); strcat(long_path_1, long_name_1); ret = DeleteFileA(long_path_1); - todo_wine ok(!ret, "Unexpected DeleteFileA successed, %s\n", long_path_1); + ok(!ret, "Unexpected DeleteFileA successed, %s\n", long_path_1); strcpy(long_path_2, temp_path); strcat(long_path_2, long_name_2); ret = DeleteFileA(long_path_2); - todo_wine ok(!ret, "Unexpected DeleteFileA successed, %s\n", long_path_2); + ok(!ret, "Unexpected DeleteFileA successed, %s\n", long_path_2);
strcpy(long_path_1, "\\?\"); strcat(long_path_1, temp_path); diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index 6db871c9483..77ac5841a71 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -1081,6 +1081,12 @@ BOOL WINAPI DECLSPEC_HOTPATCH DeleteFileW( LPCWSTR path )
TRACE( "%s\n", debugstr_w(path) );
+ if (path && !is_longpath_enabled() && wcsncmp(path, L"\\?\", 4) && wcslen(path) >= MAX_PATH ) + { + SetLastError( ERROR_PATH_NOT_FOUND ); + return FALSE; + } + if (!RtlDosPathNameToNtPathName_U( path, &nameW, NULL, NULL )) { SetLastError( ERROR_PATH_NOT_FOUND );
On Tue Mar 18 07:49:19 2025 +0000, Yongjie Yao wrote:
I understand what's going on now. Long path refers to a file whose full path length can exceed 260 characters, but the name of each file or folder cannot exceed 260 characters. The document states that relative paths are always limited to 260 characters. Previously, I misunderstood and created a file with a name exceeding 260 characters, which caused the problem of not being able to create it. The failure to test the long path using kernel32_test.exe is due to the absence of the manifest configuration file kernel32_test.exe.manifest.
@zhiyi Hello, I have processed the long path aware code and I hope you have time to review it for me.
On Tue Mar 25 01:40:29 2025 +0000, Yongjie Yao wrote:
@zhiyi Hello, I have processed the long path aware code and I hope you have time to review it for me.
Sure. Note that the code is currently failing to pass the Gitlab CI.
Zhiyi Zhang (@zhiyi) commented about dlls/user32/sysparams.c:
else SetProcessDpiAwarenessContext( DPI_AWARENESS_CONTEXT_UNAWARE ); }
- else if (QueryActCtxSettingsW( 0, NULL, L"http://schemas.microsoft.com/SMI/2016/WindowsSettings",
L"longPathAware", buffer, ARRAY_SIZE(buffer), NULL ))
- {
TRACE( "got longPathAware=%s\n", debugstr_w(buffer) );
if (!wcsicmp( buffer, L"true" ))
{
RtlGetCurrentPeb()->IsLongPathAwareProcess = 1;
I doubt user32 sets IsLongPathAwareProcess. It should be kernelbase/kernel32/ntdll because CLI applications don't load user32.