Skip to content

Commit 0d5645b

Browse files
committed
Avoid calling GetTempFileName in BitmapDownload
Replace the Win32 GetTempFileName call with Path.GetRandomFileName and CREATE_NEW in a retry loop to avoid IOException when the temp folder contains 65k+ files. This follows the same pattern already used by FileHelper.CreateAndOpenTemporaryFile elsewhere in WPF. Fixes #259
1 parent 3eeab6e commit 0d5645b

3 files changed

Lines changed: 38 additions & 45 deletions

File tree

src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Imaging/BitmapDownload.cs

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33

44
using System.IO;
55
using System.Collections;
6-
using System.ComponentModel;
76
using System.Threading;
87
using System.Windows.Threading;
98
using MS.Internal;
109
using System.Net;
1110
using System.Net.Cache;
12-
using System.Text;
1311
using MS.Win32;
1412
using Microsoft.Win32.SafeHandles;
1513

@@ -112,47 +110,55 @@ Stream stream
112110
entry.inputStream = stream;
113111

114112
string cacheFolder = MS.Win32.WinInet.InternetCacheFolder.LocalPath;
115-
bool passed = false;
116113

117-
// Get the file path
118-
StringBuilder tmpFileName = new StringBuilder(NativeMethods.MAX_PATH);
119-
MS.Win32.UnsafeNativeMethods.GetTempFileName(cacheFolder, "WPF", 0, tmpFileName);
120-
121-
try
114+
// Generate a temporary file using a random name instead of GetTempFileName,
115+
// which fails when the folder contains too many temp files (65k limit).
116+
// Retry with different random names if creation fails due to collision.
117+
const int MaxRetries = 5;
118+
SafeFileHandle fileHandle = null;
119+
string pathToUse = null;
120+
121+
for (int retries = MaxRetries; retries > 0; retries--)
122122
{
123-
string pathToUse = tmpFileName.ToString();
124-
SafeFileHandle fileHandle = MS.Win32.UnsafeNativeMethods.CreateFile(
125-
pathToUse,
126-
dwDesiredAccess: NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
127-
dwShareMode: 0,
128-
lpSecurityAttributes: null,
129-
dwCreationDisposition: NativeMethods.CREATE_ALWAYS,
130-
dwFlagsAndAttributes: NativeMethods.FILE_ATTRIBUTE_TEMPORARY | NativeMethods.FILE_FLAG_DELETE_ON_CLOSE,
131-
hTemplateFile: IntPtr.Zero
132-
);
133-
134-
if (fileHandle.IsInvalid)
123+
pathToUse = Path.Combine(cacheFolder, "WPF" + Path.GetRandomFileName());
124+
125+
try
135126
{
136-
throw new Win32Exception();
127+
fileHandle = MS.Win32.UnsafeNativeMethods.CreateFile(
128+
pathToUse,
129+
dwDesiredAccess: NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
130+
dwShareMode: 0,
131+
lpSecurityAttributes: null,
132+
dwCreationDisposition: NativeMethods.CREATE_NEW,
133+
dwFlagsAndAttributes: NativeMethods.FILE_ATTRIBUTE_TEMPORARY | NativeMethods.FILE_FLAG_DELETE_ON_CLOSE,
134+
hTemplateFile: IntPtr.Zero
135+
);
136+
137+
if (!fileHandle.IsInvalid)
138+
{
139+
break;
140+
}
141+
142+
fileHandle.Dispose();
143+
fileHandle = null;
137144
}
138-
139-
entry.outputStream = new FileStream(fileHandle, FileAccess.ReadWrite);
140-
entry.streamPath = pathToUse;
141-
passed = true;
142-
}
143-
catch(Exception e)
144-
{
145-
if (CriticalExceptions.IsCriticalException(e))
145+
catch (Exception e)
146146
{
147-
throw;
147+
if (CriticalExceptions.IsCriticalException(e))
148+
{
149+
throw;
150+
}
148151
}
149152
}
150153

151-
if (!passed)
154+
if (fileHandle == null || fileHandle.IsInvalid)
152155
{
153156
throw new IOException(SR.Image_CannotCreateTempFile);
154157
}
155158

159+
entry.outputStream = new FileStream(fileHandle, FileAccess.ReadWrite);
160+
entry.streamPath = pathToUse;
161+
156162
entry.readBuffer = new byte[READ_SIZE];
157163
entry.contentLength = -1;
158164
entry.contentType = string.Empty;

src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/NativeMethodsOther.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ protected override bool ReleaseHandle()
599599
internal const int FILE_ATTRIBUTE_TEMPORARY = 0x00000100;
600600
internal const int FILE_FLAG_DELETE_ON_CLOSE = 0x04000000;
601601

602+
internal const int CREATE_NEW = 1;
602603
internal const int CREATE_ALWAYS = 2;
603604

604605
internal const int PROCESS_ALL_ACCESS = (int)(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF);

src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/UnsafeNativeMethodsOther.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,6 @@ namespace MS.Win32
1414
{
1515
internal partial class UnsafeNativeMethods
1616
{
17-
[DllImport(ExternDll.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "GetTempFileName")]
18-
internal static extern uint _GetTempFileName(string tmpPath, string prefix, uint uniqueIdOrZero, StringBuilder tmpFileName);
19-
20-
internal static uint GetTempFileName(string tmpPath, string prefix, uint uniqueIdOrZero, StringBuilder tmpFileName)
21-
{
22-
uint result = _GetTempFileName(tmpPath, prefix, uniqueIdOrZero, tmpFileName);
23-
if (result == 0)
24-
{
25-
throw new Win32Exception();
26-
}
27-
28-
return result;
29-
}
30-
3117
[DllImport(ExternDll.Shell32, CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)]
3218
internal static extern int ExtractIconEx(
3319
string szExeFileName,

0 commit comments

Comments
 (0)