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.
-- v5: kernelbase: Limit the maximum path length for DeleteFile. kernelbase: Fix DeleteFileA doesn't support long path. kernelbase: Limit the maximum path length for filesystem. ntdll: 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..84aa4f91d42 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_OSVERSIONINFOEXW); +/* Windows 10, version 1607, building number 14393, Redstone, August 2, 2016 */ +static BOOL is_win10_1607_or_later(void) +{ + static RTL_OSVERSIONINFOEXW rovi = { 0 }; + if (rovi.dwOSVersionInfoSize == 0) + { + HMODULE hMod = GetModuleHandleW(L"ntdll.dll"); + rovi.dwOSVersionInfoSize = sizeof(rovi); + 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(void) +{ + 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(void) +{ + 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(void) +{ + 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/ntdll/actctx.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/dlls/ntdll/actctx.c b/dlls/ntdll/actctx.c index c6a4c46308c..6a885d9fb73 100644 --- a/dlls/ntdll/actctx.c +++ b/dlls/ntdll/actctx.c @@ -5252,6 +5252,7 @@ void actctx_init(void) { ACTCTXW ctx; HANDLE handle; + WCHAR buffer[5];
ctx.cbSize = sizeof(ctx); ctx.lpSource = NULL; @@ -5262,6 +5263,16 @@ void actctx_init(void) if (!RtlCreateActivationContext( &handle, &ctx )) process_actctx = check_actctx(handle);
NtCurrentTeb()->Peb->ActivationContextData = process_actctx; + + if (!RtlQueryActivationContextApplicationSettings( 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" )) + { + NtCurrentTeb()->Peb->IsLongPathAwareProcess = 1; + } + } }
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 | 67 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 84aa4f91d42..7e008540e55 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..7788056aa72 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -69,6 +69,61 @@ const WCHAR system_dir[] = L"C:\windows\system32";
static BOOL oem_file_apis;
+/* Windows 10, version 1607, building number 14393, Redstone, August 2, 2016 */ +static BOOL is_win10_1607_or_later(void) +{ + static RTL_OSVERSIONINFOEXW 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(void) +{ + 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(void) +{ + return RtlGetCurrentPeb()->IsLongPathAwareProcess != 0; +} + +static BOOL is_longpath_enabled(void) +{ + 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 +567,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 7e008540e55..4793e9d6d9f 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 7788056aa72..aec05c3cb9c 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -1058,9 +1058,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 4793e9d6d9f..db276bb9344 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 aec05c3cb9c..3535960fa73 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -1080,6 +1080,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 );
Zhiyi Zhang (@zhiyi) commented about dlls/kernelbase/file.c:
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(void)
Let's remove this function and use the comparison directly.
Zhiyi Zhang (@zhiyi) commented about dlls/kernel32/tests/file.c:
ok(ret, "DeleteFileA: error %ld\n", GetLastError());
}
+typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOEXW);
Let's move this to the top of the file where other function pointers are declared. And use pRtlGetVersion like others. And initialize it in InitFunctionPointers().
Zhiyi Zhang (@zhiyi) commented about dlls/kernel32/tests/file.c:
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(void) +{
- static DWORD LongPathEnabled = -1;
- if (LongPathEnabled == -1)
- {
DWORD ret;
HKEY hkey = (HKEY)0xdeadbeef;
No need to initialize it to 0xdeadbeef. Same at other places.
Zhiyi Zhang (@zhiyi) commented about dlls/kernel32/tests/file.c:
}
trace("in registry LongPathsEnabled: %ld\n", LongPathEnabled);
- }
- return LongPathEnabled == 1;
+}
+static BOOL is_app_enabled_long_path(void) +{
- 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};
Let's use wide-char literal L"longPathAware" instead. Same for other places. And let's move variable declarations to the top of the function.
Zhiyi Zhang (@zhiyi) commented about dlls/kernel32/tests/file.c:
- return lstrcmpiW(buffer, trueW) == 0;
+}
+static BOOL is_enabled_long_path(void) +{
- 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";
Let's just use a string of 'a's for long_name_1 and 'b's for long_name_2.
Zhiyi Zhang (@zhiyi) commented about dlls/kernel32/tests/file.c:
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);
You should probably use CopyFileExW(long_path_1, dest, ...) so that you're testing long path support for one parameter at a time.
Zhiyi Zhang (@zhiyi) commented about dlls/ntdll/actctx.c:
if (!RtlCreateActivationContext( &handle, &ctx )) process_actctx = check_actctx(handle); NtCurrentTeb()->Peb->ActivationContextData = process_actctx;
- if (!RtlQueryActivationContextApplicationSettings( 0, NULL, L"http://schemas.microsoft.com/SMI/2016/WindowsSettings",
L"longPathAware", buffer, ARRAY_SIZE(buffer), NULL ))
Keep this line aligned.
Zhiyi Zhang (@zhiyi) commented about dlls/kernelbase/file.c:
/******************************************************************************
- copy_file
*/ static BOOL copy_file( const WCHAR *source, const WCHAR *dest, COPYFILE2_EXTENDED_PARAMETERS *params )
The patch subject doesn't match what you're doing. Let's use "kernelbase: Limit the maximum path length for copy_file()."
Zhiyi Zhang (@zhiyi) commented about dlls/kernelbase/file.c:
TRACE( "%s\n", debugstr_w(path) );
- if (path && !is_longpath_enabled() && wcsncmp(path, L"\\?\", 4) && wcslen(path) >= MAX_PATH )
Do you really need to check if path is NULL? I don't see tests regarding this condition. If you do, the error should probably be ERROR_INVALID_PARAMETER.
And do you have a real-world application that needs the behavior from this MR?
Zhiyi Zhang (@zhiyi) commented about dlls/kernelbase/file.c:
{
LongPathEnabled = 0;
}
TRACE("in registry LongPathsEnabled: %ld\n", LongPathEnabled);
- }
- return LongPathEnabled == 1;
+}
+static BOOL is_longpath_enabled_in_manifest(void) +{
- return RtlGetCurrentPeb()->IsLongPathAwareProcess != 0;
+}
+static BOOL is_longpath_enabled(void) +{
- return is_win10_1607_or_later() && is_longpath_enabled_in_reg() && is_longpath_enabled_in_manifest();
What really happen should probably be RtlGetCurrentPeb()->IsLongPathAwareProcess = is_win10_1607_or_later() && is_longpath_enabled_in_reg() && is_longpath_enabled_in_manifest();
Then other functions just check RtlGetCurrentPeb()->IsLongPathAwareProcess. And, of course, please test it. You can manually test the PEB field or add a conformance test to test the field. The latter is preferred.
On Wed Mar 26 10:25:51 2025 +0000, Zhiyi Zhang wrote:
Let's remove this function and use the comparison directly.
ok
On Wed Mar 26 10:35:41 2025 +0000, Zhiyi Zhang wrote:
Let's move this to the top of the file where other function pointers are declared. And use pRtlGetVersion like others. And initialize it in InitFunctionPointers().
ok
On Wed Mar 26 10:35:42 2025 +0000, Zhiyi Zhang wrote:
No need to initialize it to 0xdeadbeef. Same at other places.
Ok.
On Wed Mar 26 10:35:42 2025 +0000, Zhiyi Zhang wrote:
Let's use wide-char literal L"longPathAware" instead. Same for other places. And let's move variable declarations to the top of the function.
Ok.
On Wed Mar 26 10:35:42 2025 +0000, Zhiyi Zhang wrote:
Let's just use a string of 'a's for long_name_1 and 'b's for long_name_2.
Ok.
On Wed Mar 26 10:35:42 2025 +0000, Zhiyi Zhang wrote:
Keep this line aligned.
Ok.
On Wed Mar 26 10:35:43 2025 +0000, Zhiyi Zhang wrote:
Do you really need to check if path is NULL? I don't see tests regarding this condition. If you do, the error should probably be ERROR_INVALID_PARAMETER.
If path is NULL, the wcsncmp function will be crashed, so really need to check if path is NULL. I will add new tests for it.
On Wed Mar 26 10:42:18 2025 +0000, Zhiyi Zhang wrote:
What really happen should probably be RtlGetCurrentPeb()->IsLongPathAwareProcess = is_win10_1607_or_later() && is_longpath_enabled_in_reg() && is_longpath_enabled_in_manifest(); Then other functions just check RtlGetCurrentPeb()->IsLongPathAwareProcess. And, of course, please test it. You can manually test the PEB field or add a conformance test to test the field. The latter is preferred.
Thank you for your advice, I will modify and test it.