From 8b88dfac45b2b12457bf39b12350859734ad82b5 Mon Sep 17 00:00:00 2001 From: Michael Rieder Date: Fri, 3 Jul 2026 12:07:43 +0200 Subject: [PATCH 1/3] add GetTempFileNameW --- dll/kernel32/fileapi.cpp | 19 ++++++++++++ dll/kernel32/fileapi.h | 1 + test/test_gettempfilename.c | 60 +++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index b65ce90..badc5fd 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -1813,6 +1813,25 @@ UINT WINAPI GetTempFileNameA(LPCSTR lpPathName, LPCSTR lpPrefixString, UINT uUni return uUnique; } +UINT WINAPI GetTempFileNameW(LPCWSTR lpPathName, LPCWSTR lpPrefixString, UINT uUnique, LPWSTR lpTempFileName) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("GetTempFileNameW -> "); + if (!lpPathName || !lpPrefixString || !lpTempFileName) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + std::string pathName = wideStringToString(lpPathName); + std::string prefixString = wideStringToString(lpPrefixString); + char tempFileName[MAX_PATH]; + UINT result = GetTempFileNameA(pathName.c_str(), prefixString.c_str(), uUnique, tempFileName); + if (result == 0) { + return 0; + } + auto wide = stringToWideString(tempFileName); + wstrncpy(lpTempFileName, wide.data(), wstrlen(wide.data()) + 1); + return result; +} + DWORD WINAPI GetTempPathA(DWORD nBufferLength, LPSTR lpBuffer) { HOST_CONTEXT_GUARD(); DEBUG_LOG("GetTempPathA(%u, %p)\n", nBufferLength, lpBuffer); diff --git a/dll/kernel32/fileapi.h b/dll/kernel32/fileapi.h index 7531963..0e1d443 100644 --- a/dll/kernel32/fileapi.h +++ b/dll/kernel32/fileapi.h @@ -61,6 +61,7 @@ DWORD WINAPI GetFullPathNameW(LPCWSTR lpFileName, DWORD nBufferLength, LPWSTR lp DWORD WINAPI GetShortPathNameA(LPCSTR lpszLongPath, LPSTR lpszShortPath, DWORD cchBuffer); DWORD WINAPI GetShortPathNameW(LPCWSTR lpszLongPath, LPWSTR lpszShortPath, DWORD cchBuffer); UINT WINAPI GetTempFileNameA(LPCSTR lpPathName, LPCSTR lpPrefixString, UINT uUnique, LPSTR lpTempFileName); +UINT WINAPI GetTempFileNameW(LPCWSTR lpPathName, LPCWSTR lpPrefixString, UINT uUnique, LPWSTR lpTempFileName); DWORD WINAPI GetTempPathA(DWORD nBufferLength, LPSTR lpBuffer); HANDLE WINAPI FindFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData); HANDLE WINAPI FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData); diff --git a/test/test_gettempfilename.c b/test/test_gettempfilename.c index 776a4d6..4aec3a6 100644 --- a/test/test_gettempfilename.c +++ b/test/test_gettempfilename.c @@ -2,6 +2,7 @@ #include #include +#include #include static const char *basename_of(const char *path) { @@ -9,6 +10,11 @@ static const char *basename_of(const char *path) { return slash ? slash + 1 : path; } +static const WCHAR *basename_of_w(const WCHAR *path) { + const WCHAR *slash = wcsrchr(path, L'\\'); + return slash ? slash + 1 : path; +} + static void expect_fixed_unique_name(const char *temp_path) { char temp_file[MAX_PATH]; @@ -39,6 +45,52 @@ static void expect_created_file_reopens_like_link(const char *temp_path) { TEST_CHECK(CloseHandle(handle)); } +static void expect_fixed_unique_name_w(const WCHAR *temp_path) { + WCHAR temp_file[MAX_PATH]; + + UINT result = GetTempFileNameW(temp_path, L"wboX", 0x12345, temp_file); + TEST_CHECK_EQ(0x12345, result); + DeleteFileW(temp_file); + + result = GetTempFileNameW(temp_path, L"wboX", 0x12345, temp_file); + TEST_CHECK_EQ(0x12345, result); + TEST_CHECK(wcscmp(L"wbo2345.TMP", basename_of_w(temp_file)) == 0); + TEST_CHECK_EQ(INVALID_FILE_ATTRIBUTES, GetFileAttributesW(temp_file)); +} + +static void expect_created_file_reopens_like_link_w(const WCHAR *temp_path) { + WCHAR temp_file[MAX_PATH]; + + UINT result = GetTempFileNameW(temp_path, L"GDI32", 0, temp_file); + TEST_CHECK(result != 0); + TEST_CHECK_EQ(11, wcslen(basename_of_w(temp_file))); + TEST_CHECK_EQ(0, wcsncmp(L"GDI", basename_of_w(temp_file), 3)); + TEST_CHECK(wcscmp(L".TMP", basename_of_w(temp_file) + 7) == 0); + TEST_CHECK(GetFileAttributesW(temp_file) != INVALID_FILE_ATTRIBUTES); + + HANDLE handle = CreateFileW(temp_file, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL); + TEST_CHECK_MSG(handle != INVALID_HANDLE_VALUE, "CreateFileW(temp, OPEN_EXISTING|DELETE_ON_CLOSE) failed: %lu", + GetLastError()); + TEST_CHECK(CloseHandle(handle)); +} + +static void expect_invalid_parameters_w(const WCHAR *temp_path) { + WCHAR temp_file[MAX_PATH]; + + SetLastError(0); + TEST_CHECK_EQ(0, GetTempFileNameW(NULL, L"wbo", 0, temp_file)); + TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); + + SetLastError(0); + TEST_CHECK_EQ(0, GetTempFileNameW(temp_path, NULL, 0, temp_file)); + TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); + + SetLastError(0); + TEST_CHECK_EQ(0, GetTempFileNameW(temp_path, L"wbo", 0, NULL)); + TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); +} + int main(void) { char temp_path[MAX_PATH]; DWORD len = GetTempPathA(sizeof(temp_path), temp_path); @@ -47,5 +99,13 @@ int main(void) { expect_fixed_unique_name(temp_path); expect_created_file_reopens_like_link(temp_path); + WCHAR temp_path_w[MAX_PATH]; + int wlen = MultiByteToWideChar(CP_ACP, 0, temp_path, -1, temp_path_w, MAX_PATH); + TEST_CHECK(wlen > 0); + + expect_fixed_unique_name_w(temp_path_w); + expect_created_file_reopens_like_link_w(temp_path_w); + expect_invalid_parameters_w(temp_path_w); + return 0; } From 2c331c0747ca93c2ce2909220dddcf3cfd814707 Mon Sep 17 00:00:00 2001 From: Michael Rieder Date: Sat, 11 Jul 2026 14:16:45 +0200 Subject: [PATCH 2/3] Treat NULL `lpPrefixString` as empty string in `GetTempFileNameA` and `GetTempFileNameW` --- dll/kernel32/fileapi.cpp | 12 ++++++++---- test/test_gettempfilename.c | 22 ++++++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index badc5fd..a742d0b 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -1748,7 +1748,7 @@ UINT WINAPI GetTempFileNameA(LPCSTR lpPathName, LPCSTR lpPrefixString, UINT uUni HOST_CONTEXT_GUARD(); DEBUG_LOG("GetTempFileNameA(%s, %s, %u)\n", lpPathName ? lpPathName : "(null)", lpPrefixString ? lpPrefixString : "(null)", uUnique); - if (!lpPathName || !lpPrefixString || !lpTempFileName) { + if (!lpPathName || !lpTempFileName) { setLastError(ERROR_INVALID_PARAMETER); return 0; } @@ -1757,9 +1757,12 @@ UINT WINAPI GetTempFileNameA(LPCSTR lpPathName, LPCSTR lpPrefixString, UINT uUni return 0; } + // A null prefix is treated as an empty string, matching Windows/Wine. + const char *prefix = lpPrefixString ? lpPrefixString : ""; + auto makeTempFileName = [&](UINT unique) { char filename[12]; - snprintf(filename, sizeof(filename), "%.3s%04X.TMP", lpPrefixString, unique & 0xFFFF); + snprintf(filename, sizeof(filename), "%.3s%04X.TMP", prefix, unique & 0xFFFF); std::string path = lpPathName; if (!path.empty() && path.back() != '\\' && path.back() != '/') { @@ -1816,12 +1819,13 @@ UINT WINAPI GetTempFileNameA(LPCSTR lpPathName, LPCSTR lpPrefixString, UINT uUni UINT WINAPI GetTempFileNameW(LPCWSTR lpPathName, LPCWSTR lpPrefixString, UINT uUnique, LPWSTR lpTempFileName) { HOST_CONTEXT_GUARD(); DEBUG_LOG("GetTempFileNameW -> "); - if (!lpPathName || !lpPrefixString || !lpTempFileName) { + if (!lpPathName || !lpTempFileName) { setLastError(ERROR_INVALID_PARAMETER); return 0; } std::string pathName = wideStringToString(lpPathName); - std::string prefixString = wideStringToString(lpPrefixString); + // A null prefix is treated as an empty string, matching Windows/Wine. + std::string prefixString = lpPrefixString ? wideStringToString(lpPrefixString) : std::string(); char tempFileName[MAX_PATH]; UINT result = GetTempFileNameA(pathName.c_str(), prefixString.c_str(), uUnique, tempFileName); if (result == 0) { diff --git a/test/test_gettempfilename.c b/test/test_gettempfilename.c index 4aec3a6..1ebf802 100644 --- a/test/test_gettempfilename.c +++ b/test/test_gettempfilename.c @@ -45,6 +45,14 @@ static void expect_created_file_reopens_like_link(const char *temp_path) { TEST_CHECK(CloseHandle(handle)); } +static void expect_null_prefix_is_empty(const char *temp_path) { + char temp_file[MAX_PATH]; + + UINT result = GetTempFileNameA(temp_path, NULL, 0x12345, temp_file); + TEST_CHECK_EQ(0x12345, result); + TEST_CHECK_STR_EQ("2345.TMP", basename_of(temp_file)); +} + static void expect_fixed_unique_name_w(const WCHAR *temp_path) { WCHAR temp_file[MAX_PATH]; @@ -75,6 +83,14 @@ static void expect_created_file_reopens_like_link_w(const WCHAR *temp_path) { TEST_CHECK(CloseHandle(handle)); } +static void expect_null_prefix_is_empty_w(const WCHAR *temp_path) { + WCHAR temp_file[MAX_PATH]; + + UINT result = GetTempFileNameW(temp_path, NULL, 0x12345, temp_file); + TEST_CHECK_EQ(0x12345, result); + TEST_CHECK(wcscmp(L"2345.TMP", basename_of_w(temp_file)) == 0); +} + static void expect_invalid_parameters_w(const WCHAR *temp_path) { WCHAR temp_file[MAX_PATH]; @@ -82,10 +98,6 @@ static void expect_invalid_parameters_w(const WCHAR *temp_path) { TEST_CHECK_EQ(0, GetTempFileNameW(NULL, L"wbo", 0, temp_file)); TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); - SetLastError(0); - TEST_CHECK_EQ(0, GetTempFileNameW(temp_path, NULL, 0, temp_file)); - TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); - SetLastError(0); TEST_CHECK_EQ(0, GetTempFileNameW(temp_path, L"wbo", 0, NULL)); TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); @@ -98,6 +110,7 @@ int main(void) { expect_fixed_unique_name(temp_path); expect_created_file_reopens_like_link(temp_path); + expect_null_prefix_is_empty(temp_path); WCHAR temp_path_w[MAX_PATH]; int wlen = MultiByteToWideChar(CP_ACP, 0, temp_path, -1, temp_path_w, MAX_PATH); @@ -105,6 +118,7 @@ int main(void) { expect_fixed_unique_name_w(temp_path_w); expect_created_file_reopens_like_link_w(temp_path_w); + expect_null_prefix_is_empty_w(temp_path_w); expect_invalid_parameters_w(temp_path_w); return 0; From 3c4be9e307f4d59ad2a71a2913dbb8f24b5a9e02 Mon Sep 17 00:00:00 2001 From: Michael Rieder Date: Sat, 11 Jul 2026 15:23:07 +0200 Subject: [PATCH 3/3] Fix GetTempFileName unique value truncation --- dll/kernel32/fileapi.cpp | 2 +- test/test_gettempfilename.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index a742d0b..31b5083 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -1813,7 +1813,7 @@ UINT WINAPI GetTempFileNameA(LPCSTR lpPathName, LPCSTR lpPrefixString, UINT uUni lpTempFileName[MAX_PATH - 1] = '\0'; DEBUG_LOG(" -> %s\n", lpTempFileName); } - return uUnique; + return uUnique & 0xFFFF; } UINT WINAPI GetTempFileNameW(LPCWSTR lpPathName, LPCWSTR lpPrefixString, UINT uUnique, LPWSTR lpTempFileName) { diff --git a/test/test_gettempfilename.c b/test/test_gettempfilename.c index 1ebf802..85a1e25 100644 --- a/test/test_gettempfilename.c +++ b/test/test_gettempfilename.c @@ -19,11 +19,11 @@ static void expect_fixed_unique_name(const char *temp_path) { char temp_file[MAX_PATH]; UINT result = GetTempFileNameA(temp_path, "wboX", 0x12345, temp_file); - TEST_CHECK_EQ(0x12345, result); + TEST_CHECK_EQ(0x2345, result); DeleteFileA(temp_file); result = GetTempFileNameA(temp_path, "wboX", 0x12345, temp_file); - TEST_CHECK_EQ(0x12345, result); + TEST_CHECK_EQ(0x2345, result); TEST_CHECK_STR_EQ("wbo2345.TMP", basename_of(temp_file)); TEST_CHECK_EQ(INVALID_FILE_ATTRIBUTES, GetFileAttributesA(temp_file)); } @@ -49,7 +49,7 @@ static void expect_null_prefix_is_empty(const char *temp_path) { char temp_file[MAX_PATH]; UINT result = GetTempFileNameA(temp_path, NULL, 0x12345, temp_file); - TEST_CHECK_EQ(0x12345, result); + TEST_CHECK_EQ(0x2345, result); TEST_CHECK_STR_EQ("2345.TMP", basename_of(temp_file)); } @@ -57,11 +57,11 @@ static void expect_fixed_unique_name_w(const WCHAR *temp_path) { WCHAR temp_file[MAX_PATH]; UINT result = GetTempFileNameW(temp_path, L"wboX", 0x12345, temp_file); - TEST_CHECK_EQ(0x12345, result); + TEST_CHECK_EQ(0x2345, result); DeleteFileW(temp_file); result = GetTempFileNameW(temp_path, L"wboX", 0x12345, temp_file); - TEST_CHECK_EQ(0x12345, result); + TEST_CHECK_EQ(0x2345, result); TEST_CHECK(wcscmp(L"wbo2345.TMP", basename_of_w(temp_file)) == 0); TEST_CHECK_EQ(INVALID_FILE_ATTRIBUTES, GetFileAttributesW(temp_file)); } @@ -87,7 +87,7 @@ static void expect_null_prefix_is_empty_w(const WCHAR *temp_path) { WCHAR temp_file[MAX_PATH]; UINT result = GetTempFileNameW(temp_path, NULL, 0x12345, temp_file); - TEST_CHECK_EQ(0x12345, result); + TEST_CHECK_EQ(0x2345, result); TEST_CHECK(wcscmp(L"2345.TMP", basename_of_w(temp_file)) == 0); }