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.
-- v2: kernelbase: Limit the maximum path length for DeleteFile. kernelbase: Fix DeleteFileA doesn't support long path. kernelbase: Limit the maximum path length for filesystem. 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. --- dlls/kernel32/tests/file.c | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 68b9f53700e..4b6fbbcf867 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -910,6 +910,12 @@ static void test_CopyFileW(void) 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 +948,40 @@ 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. */ + 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)); + ret = DeleteFileW(long_path_1); + ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + ret = DeleteFileW(long_path_2); + ok(ret, "DeleteFileW: error %ld\n", GetLastError()); + ret = DeleteFileW(source); ok(ret, "DeleteFileW: error %ld\n", GetLastError()); ret = DeleteFileW(dest); @@ -1194,6 +1234,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 +1281,40 @@ 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. */ + 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); + ret = DeleteFileA(long_path_1); + todo_wine ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); + 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);
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 | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 4b6fbbcf867..987faf7468f 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -953,12 +953,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); @@ -1286,12 +1286,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 f0dedfe3b14..c3d87a84fd5 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -512,6 +512,18 @@ static BOOL copy_file( const WCHAR *source, const WCHAR *dest, COPYFILE2_EXTENDE SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } + + if (wcsncmp(source, L"\\?\", 4) && wcslen(source) >= MAX_PATH ) + { + SetLastError( ERROR_PATH_NOT_FOUND ); + return FALSE; + } + if (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 | 4 ++-- dlls/kernelbase/file.c | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 987faf7468f..f9c4d5583c5 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -1311,9 +1311,9 @@ static void test_CopyFileEx(void) 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); 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()); 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); ok(ret, "DeleteFileA failed with error %ld\n", GetLastError()); diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index c3d87a84fd5..ab601246d28 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -1003,9 +1003,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 | 8 ++++---- dlls/kernelbase/file.c | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index f9c4d5583c5..65107a09543 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -960,9 +960,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 "\?" */ wcscpy(long_path_1, L"\\?\"); @@ -1293,9 +1293,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 "\?" */ strcpy(long_path_1, "\\?\"); diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index ab601246d28..12532ce5328 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -1025,6 +1025,12 @@ BOOL WINAPI DECLSPEC_HOTPATCH DeleteFileW( LPCWSTR path )
TRACE( "%s\n", debugstr_w(path) );
+ if (path && 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 );
What if the process is long-path-aware?
This is not addressed.
On Wed Mar 12 14:18:34 2025 +0000, Zhiyi Zhang wrote:
What if the process is long-path-aware?
This is not addressed.
Yes, you are right. I will fix it.
But I found that even after setting up the registry on Windows 10 22H2 Home Edition, it still does not support long paths. I will try Windows 10 Professional Edition again.
On Thu Mar 13 02:44:51 2025 +0000, Yongjie Yao wrote:
Yes, you are right. I will fix it. But I found that even after setting up the registry on Windows 10 22H2 Home Edition, it still does not support long paths. I will try Windows 10 Professional Edition again.
You should add a manifest to the application. See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-lim.... See Wine's programs/notepad/notepad.rc with regards to how to add a manifest.
On Thu Mar 13 03:07:44 2025 +0000, Zhiyi Zhang wrote:
You should add a manifest to the application. See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-lim.... See Wine's programs/notepad/notepad.rc with regards to how to add a manifest.
I have encountered a problem, how should I verify the long path restriction issue on Windows? I have enabled long paths on virtual machines win10 21H2 and win11 24H2 respectively, and set the value of LongPathsEnable in “HKEYLOCAL\SYSTEM\CurrentControlSet\Control\FileSystem” to 1. However, whether using Python 3.13's open() function, Neither the copy command nor notepad.exe can create a file with a path length of 500. I also tried adding the following content to the manifests of wine's notepad.exe and kernel32_test.exe, but still couldn't create files with path lengths exceeding 260 on Windows 10 and Windows 11.
``` <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> ws2:longPathAwaretrue</ws2:longPathAware> </windowsSettings> </application> ```
On Fri Mar 14 10:11:36 2025 +0000, Yongjie Yao wrote:
I have encountered a problem, how should I verify the long path restriction issue on Windows? I have enabled long paths on virtual machines win10 21H2 and win11 24H2 respectively, and set the value of LongPathsEnable in “HKEYLOCAL\SYSTEM\CurrentControlSet\Control\FileSystem” to 1. However, whether using Python 3.13's open() function, Neither the copy command nor notepad.exe can create a file with a path length of 500. I also tried adding the following content to the manifests of wine's notepad.exe and kernel32_test.exe, but still couldn't create files with path lengths exceeding 260 on Windows 10 and Windows 11.
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> <ws2:longPathAware>true</ws2:longPathAware> </windowsSettings> </application>
Did you follow everything shown on the MSDN page?
On Fri Mar 14 10:49:40 2025 +0000, Zhiyi Zhang wrote:
Did you follow everything shown on the MSDN page?
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.