diff --git a/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs b/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs
index 137b39405962ed..f0dee88a4b61dc 100644
--- a/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs
+++ b/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs
@@ -45,6 +45,14 @@ internal static long CheckIo(long result, string? path = null, bool isDirectory
return result;
}
+ ///
+ /// Throws an IOException using the last error info (errno).
+ ///
+ internal static void ThrowIOExceptionForLastError()
+ {
+ ThrowExceptionForIoErrno(Sys.GetLastErrorInfo(), path: null, isDirectory: false);
+ }
+
///
/// Validates the result of system call that returns greater than or equal to 0 on success
/// and less than 0 on failure, with errno set to the error code.
diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MkdTemp.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MkdTemp.cs
new file mode 100644
index 00000000000000..59d6ace2dfa277
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MkdTemp.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_MkdTemp", SetLastError = true)]
+ internal static unsafe partial byte* MkdTemp(byte* template);
+ }
+}
diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MksTemps.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MksTemps.cs
index 95250e6c27aa0e..1ce54b66f560f6 100644
--- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MksTemps.cs
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MksTemps.cs
@@ -9,8 +9,8 @@ internal static partial class Interop
internal static partial class Sys
{
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_MksTemps", SetLastError = true)]
- internal static partial IntPtr MksTemps(
- byte[] template,
+ internal static unsafe partial IntPtr MksTemps(
+ byte* template,
int suffixlen);
}
}
diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateDirectory.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateDirectory.cs
index c679fbbb023dbd..2b889d797a1033 100644
--- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateDirectory.cs
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateDirectory.cs
@@ -14,15 +14,15 @@ internal static partial class Kernel32
///
[LibraryImport(Libraries.Kernel32, EntryPoint = "CreateDirectoryW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
- private static partial bool CreateDirectoryPrivate(
+ private static unsafe partial bool CreateDirectoryPrivate(
string path,
- ref SECURITY_ATTRIBUTES lpSecurityAttributes);
+ SECURITY_ATTRIBUTES* lpSecurityAttributes);
- internal static bool CreateDirectory(string path, ref SECURITY_ATTRIBUTES lpSecurityAttributes)
+ internal static unsafe bool CreateDirectory(string path, SECURITY_ATTRIBUTES* lpSecurityAttributes)
{
// We always want to add for CreateDirectory to get around the legacy 248 character limitation
path = PathInternal.EnsureExtendedPrefix(path);
- return CreateDirectoryPrivate(path, ref lpSecurityAttributes);
+ return CreateDirectoryPrivate(path, lpSecurityAttributes);
}
}
}
diff --git a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs
index dba07a8fb41567..708714ab2401ff 100644
--- a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs
+++ b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs
@@ -88,7 +88,7 @@ public static unsafe void CreateDirectory(string fullPath, byte[]? securityDescr
string name = stackDir[stackDir.Count - 1];
stackDir.RemoveAt(stackDir.Count - 1);
- r = Interop.Kernel32.CreateDirectory(name, ref secAttrs);
+ r = Interop.Kernel32.CreateDirectory(name, &secAttrs);
if (!r && (firstError == 0))
{
int currentError = Marshal.GetLastWin32Error();
diff --git a/src/libraries/Common/src/System/IO/PathInternal.Unix.cs b/src/libraries/Common/src/System/IO/PathInternal.Unix.cs
index 912db66d56d16c..cc26dd25b566a5 100644
--- a/src/libraries/Common/src/System/IO/PathInternal.Unix.cs
+++ b/src/libraries/Common/src/System/IO/PathInternal.Unix.cs
@@ -16,6 +16,7 @@ internal static partial class PathInternal
internal const char PathSeparator = ':';
internal const string DirectorySeparatorCharAsString = "/";
internal const string ParentDirectoryPrefix = @"../";
+ internal const string DirectorySeparators = DirectorySeparatorCharAsString;
internal static int GetRootLength(ReadOnlySpan path)
{
diff --git a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs
index 9015df805583e5..48c5509ef6126b 100644
--- a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs
+++ b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs
@@ -54,6 +54,7 @@ internal static partial class PathInternal
internal const string UncNTPathPrefix = @"\??\UNC\";
internal const string DevicePathPrefix = @"\\.\";
internal const string ParentDirectoryPrefix = @"..\";
+ internal const string DirectorySeparators = @"\/";
internal const int MaxShortPath = 260;
internal const int MaxShortDirectoryPath = 248;
diff --git a/src/libraries/Common/src/System/IO/TempFileCollection.cs b/src/libraries/Common/src/System/IO/TempFileCollection.cs
index 0611b259485cb9..36db27afbcb7a0 100644
--- a/src/libraries/Common/src/System/IO/TempFileCollection.cs
+++ b/src/libraries/Common/src/System/IO/TempFileCollection.cs
@@ -24,6 +24,7 @@ class TempFileCollection : ICollection, IDisposable
private string _basePath;
private readonly string _tempDir;
private readonly Hashtable _files;
+ private bool _createdTempDirectory;
public TempFileCollection() : this(null, false)
{
@@ -127,7 +128,7 @@ private void EnsureTempNameCreated()
do
{
_basePath = Path.Combine(
- string.IsNullOrEmpty(TempDir) ? Path.GetTempPath() : TempDir,
+ string.IsNullOrEmpty(TempDir) ? GetTempDirectory() : TempDir,
Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
tempFileName = _basePath + ".tmp";
@@ -150,6 +151,19 @@ private void EnsureTempNameCreated()
}
}
+#if NET7_0_OR_GREATER
+ private string GetTempDirectory()
+ {
+ _createdTempDirectory = true;
+ return Directory.CreateTempSubdirectory().FullName;
+ }
+#else
+ private static string GetTempDirectory()
+ {
+ return Path.GetTempPath();
+ }
+#endif
+
public bool KeepFiles { get; set; }
private bool KeepFile(string fileName)
@@ -177,6 +191,8 @@ private static void Delete(string fileName)
internal void SafeDelete()
{
+ bool allFilesDeleted = true;
+
if (_files != null && _files.Count > 0)
{
string[] fileNames = new string[_files.Count];
@@ -188,8 +204,28 @@ internal void SafeDelete()
Delete(fileName);
_files.Remove(fileName);
}
+ else
+ {
+ allFilesDeleted = false;
+ }
}
}
+
+ // if we created a temp directory, delete it and clear the basePath, so a new directory will be created for the next request.
+ if (_createdTempDirectory && allFilesDeleted)
+ {
+ try
+ {
+ Directory.Delete(Path.GetDirectoryName(BasePath));
+ }
+ catch
+ {
+ // Ignore all exceptions
+ }
+
+ _createdTempDirectory = false;
+ _basePath = null;
+ }
}
}
}
diff --git a/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/DisposableFileSystem.cs b/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/DisposableFileSystem.cs
index 73f131afb3fa32..fffca558b0c74d 100644
--- a/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/DisposableFileSystem.cs
+++ b/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/DisposableFileSystem.cs
@@ -14,9 +14,13 @@ public class DisposableFileSystem : IDisposable
public DisposableFileSystem()
{
- RootPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
- CreateFolder("");
- DirectoryInfo = new DirectoryInfo(RootPath);
+#if NETCOREAPP
+ DirectoryInfo = Directory.CreateTempSubdirectory();
+#else
+ DirectoryInfo = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
+ DirectoryInfo.Create();
+#endif
+ RootPath = DirectoryInfo.FullName;
}
public string RootPath { get; }
diff --git a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/TestUtility/DisposableFileSystem.cs b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/TestUtility/DisposableFileSystem.cs
index 86baaa0b14a6ab..50abfa57af42e3 100644
--- a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/TestUtility/DisposableFileSystem.cs
+++ b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/TestUtility/DisposableFileSystem.cs
@@ -10,9 +10,13 @@ public class DisposableFileSystem : IDisposable
{
public DisposableFileSystem()
{
- RootPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
- Directory.CreateDirectory(RootPath);
- DirectoryInfo = new DirectoryInfo(RootPath);
+#if NETCOREAPP
+ DirectoryInfo = Directory.CreateTempSubdirectory();
+#else
+ DirectoryInfo = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
+ DirectoryInfo.Create();
+#endif
+ RootPath = DirectoryInfo.FullName;
}
public string RootPath { get; }
diff --git a/src/libraries/System.CodeDom/tests/System/CodeDom/TempFileCollectionTests.cs b/src/libraries/System.CodeDom/tests/System/CodeDom/TempFileCollectionTests.cs
index f79ef1bb39c497..7162c9358fdd3c 100644
--- a/src/libraries/System.CodeDom/tests/System/CodeDom/TempFileCollectionTests.cs
+++ b/src/libraries/System.CodeDom/tests/System/CodeDom/TempFileCollectionTests.cs
@@ -277,8 +277,13 @@ private static string TempDirectory()
{
if (s_tempDirectory == null)
{
+#if NETCOREAPP
+ string tempDirectory = Directory.CreateTempSubdirectory().FullName;
+#else
string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDirectory);
+#endif
+
s_tempDirectory = tempDirectory;
}
return s_tempDirectory;
diff --git a/src/libraries/System.ComponentModel.Composition/tests/System/ComponentModel/Composition/Hosting/DirectoryCatalogDebuggerProxyTests.cs b/src/libraries/System.ComponentModel.Composition/tests/System/ComponentModel/Composition/Hosting/DirectoryCatalogDebuggerProxyTests.cs
index b91366935d5c44..0a69ae32ae2114 100644
--- a/src/libraries/System.ComponentModel.Composition/tests/System/ComponentModel/Composition/Hosting/DirectoryCatalogDebuggerProxyTests.cs
+++ b/src/libraries/System.ComponentModel.Composition/tests/System/ComponentModel/Composition/Hosting/DirectoryCatalogDebuggerProxyTests.cs
@@ -164,9 +164,7 @@ private DirectoryCatalog CreateDirectoryCatalog(string path, string filter)
private string GetTemporaryDirectory(string location = null)
{
- string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
- Directory.CreateDirectory(tempDirectory);
- return tempDirectory;
+ return Directory.CreateTempSubdirectory().FullName;
}
}
@@ -193,16 +191,12 @@ public static string GetRootTemporaryDirectory()
public static string GetNewTemporaryDirectory()
{
- string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
- Directory.CreateDirectory(tempDirectory);
- return tempDirectory;
+ return Directory.CreateTempSubdirectory().FullName;
}
public static string GetTemporaryDirectory()
{
- string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
- Directory.CreateDirectory(tempDirectory);
- return tempDirectory;
+ return Directory.CreateTempSubdirectory().FullName;
}
}
}
diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateTempSubdirectory.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateTempSubdirectory.cs
new file mode 100644
index 00000000000000..2195693da87122
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateTempSubdirectory.cs
@@ -0,0 +1,112 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+
+namespace System.IO.Tests
+{
+ public class Directory_CreateTempSubdirectory : FileSystemTest
+ {
+ public static TheoryData CreateTempSubdirectoryData
+ {
+ get
+ {
+ var result = new TheoryData() { null, "", "myDir", "my.Dir", "H\u00EBllo" };
+ if (!OperatingSystem.IsWindows())
+ {
+ // ensure we can use backslashes on Unix since that isn't a directory separator
+ result.Add(@"my\File");
+ result.Add(@"\");
+ }
+ return result;
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(CreateTempSubdirectoryData))]
+ public void CreateTempSubdirectory(string prefix)
+ {
+ DirectoryInfo tmpDir = Directory.CreateTempSubdirectory(prefix);
+ try
+ {
+ Assert.True(tmpDir.Exists);
+ Assert.Equal(-1, tmpDir.FullName.IndexOfAny(Path.GetInvalidPathChars()));
+ Assert.Empty(Directory.GetFileSystemEntries(tmpDir.FullName));
+ Assert.Equal(Path.TrimEndingDirectorySeparator(Path.GetTempPath()), tmpDir.Parent.FullName);
+
+ if (!string.IsNullOrEmpty(prefix))
+ {
+ Assert.StartsWith(prefix, tmpDir.Name);
+ int expectedNameLength = prefix.Length + (OperatingSystem.IsWindows() ? 12 : 6);
+ Assert.Equal(expectedNameLength, tmpDir.Name.Length);
+ }
+
+ if (!OperatingSystem.IsWindows())
+ {
+ UnixFileMode userRWX = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute;
+ Assert.Equal(userRWX, tmpDir.UnixFileMode);
+ }
+
+ // Ensure a file can be written to the directory
+ string tempFile = Path.Combine(tmpDir.FullName, "newFile");
+ using (FileStream fs = File.Create(tempFile, bufferSize: 1024, FileOptions.DeleteOnClose))
+ {
+ Assert.Equal(0, fs.Length);
+ }
+ }
+ finally
+ {
+ tmpDir.Delete(recursive: true);
+ }
+ }
+
+ [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ public void CreateTempSubdirectoryTempUnicode()
+ {
+ RemoteExecutor.Invoke(() =>
+ {
+ DirectoryInfo tempPathWithUnicode = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + "\u00F6"));
+ tempPathWithUnicode.Create();
+
+ string tempEnvVar = OperatingSystem.IsWindows() ? "TMP" : "TMPDIR";
+ Environment.SetEnvironmentVariable(tempEnvVar, tempPathWithUnicode.FullName);
+
+ try
+ {
+ DirectoryInfo tmpDir = Directory.CreateTempSubdirectory();
+ Assert.True(tmpDir.Exists);
+ Assert.Equal(tempPathWithUnicode.FullName, tmpDir.Parent.FullName);
+
+ Environment.SetEnvironmentVariable(tempEnvVar, tempPathWithUnicode.Parent.FullName);
+ }
+ finally
+ {
+ tempPathWithUnicode.Delete(recursive: true);
+ }
+ }).Dispose();
+ }
+
+ public static TheoryData InvalidPrefixData
+ {
+ get
+ {
+ var result = new TheoryData() { "/", "myDir/", "my/Dir" };
+ if (OperatingSystem.IsWindows())
+ {
+ result.Add(@"\");
+ result.Add(@"myDir\");
+ result.Add(@"my\Dir");
+ }
+ return result;
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(InvalidPrefixData))]
+ public void CreateTempSubdirectoryThrowsWithPrefixContainingDirectorySeparator(string prefix)
+ {
+ AssertExtensions.Throws("prefix", () => Directory.CreateTempSubdirectory(prefix));
+ }
+ }
+}
diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
index a548e0b7cc90e6..e386671171d19d 100644
--- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
+++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
@@ -19,6 +19,7 @@
+
diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index 3fc2d2014809e2..53ac7df9179cba 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -2710,6 +2710,9 @@
[Unknown]
+
+ Unable to complete the operation after the maximum number of attempts.
+
The lazily-initialized type does not have a public, parameterless constructor.
@@ -3988,4 +3991,7 @@
Cannot load assembly '{0}'. No metadata found for this assembly.
+
+ The value may not contain directory separator characters.
+
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 11ca7cec9d6988..fefbe27ae67c68 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -2093,6 +2093,9 @@
Common\Interop\Unix\System.Native\Interop.MkDir.cs
+
+ Common\Interop\Unix\System.Native\Interop.MkdTemp.cs
+
Common\Interop\Unix\System.Native\Interop.MksTemps.cs
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs
index d874d1c8c9bc30..26116cd19e4f78 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs
@@ -1,6 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+using System.Text;
+
namespace System.IO
{
public static partial class Directory
@@ -20,5 +23,37 @@ private static DirectoryInfo CreateDirectoryCore(string path, UnixFileMode unixC
return new DirectoryInfo(path, fullPath, isNormalized: true);
}
+
+ private static unsafe string CreateTempSubdirectoryCore(string? prefix)
+ {
+ // mkdtemp takes a char* and overwrites the XXXXXX with six characters
+ // that'll result in a unique directory name.
+ string tempPath = Path.GetTempPath();
+ int tempPathByteCount = Encoding.UTF8.GetByteCount(tempPath);
+ int prefixByteCount = prefix is not null ? Encoding.UTF8.GetByteCount(prefix) : 0;
+ int totalByteCount = tempPathByteCount + prefixByteCount + 6 + 1;
+
+ Span path = totalByteCount <= 256 ? stackalloc byte[256].Slice(0, totalByteCount) : new byte[totalByteCount];
+ int pos = Encoding.UTF8.GetBytes(tempPath, path);
+ if (prefix is not null)
+ {
+ pos += Encoding.UTF8.GetBytes(prefix, path.Slice(pos));
+ }
+ path.Slice(pos, 6).Fill((byte)'X');
+ path[pos + 6] = 0;
+
+ // Create the temp directory.
+ fixed (byte* pPath = path)
+ {
+ if (Interop.Sys.MkdTemp(pPath) is null)
+ {
+ Interop.ThrowIOExceptionForLastError();
+ }
+ }
+
+ // 'path' is now the name of the directory
+ Debug.Assert(path[^1] == 0);
+ return Encoding.UTF8.GetString(path.Slice(0, path.Length - 1)); // trim off the trailing '\0'
+ }
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Windows.cs
index 0f96417902ffce..8861c97ca1d3d3 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Windows.cs
@@ -1,11 +1,67 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Runtime.InteropServices;
+using System.Text;
+
namespace System.IO
{
public static partial class Directory
{
private static DirectoryInfo CreateDirectoryCore(string path, UnixFileMode unixCreateMode)
=> throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode);
+
+ private static unsafe string CreateTempSubdirectoryCore(string? prefix)
+ {
+ ValueStringBuilder builder = new ValueStringBuilder(stackalloc char[PathInternal.MaxShortPath]);
+ Path.GetTempPath(ref builder);
+
+ // ensure the base TEMP directory exists
+ CreateDirectory(PathHelper.Normalize(ref builder));
+
+ builder.Append(prefix);
+
+ const int RandomFileNameLength = 12; // 12 == 8 + 1 (for period) + 3
+ int initialTempPathLength = builder.Length;
+ builder.EnsureCapacity(initialTempPathLength + RandomFileNameLength);
+
+ // For generating random file names
+ // 8 random bytes provides 12 chars in our encoding for the 8.3 name.
+ const int RandomKeyLength = 8;
+ byte* pKey = stackalloc byte[RandomKeyLength];
+
+ // to avoid an infinite loop, only try as many as GetTempFileNameW will create
+ const int MaxAttempts = ushort.MaxValue;
+ int attempts = 0;
+ while (attempts < MaxAttempts)
+ {
+ // simulate a call to Path.GetRandomFileName() without allocating an intermediate string
+ Interop.GetRandomBytes(pKey, RandomKeyLength);
+ Path.Populate83FileNameFromRandomBytes(pKey, RandomKeyLength, builder.RawChars.Slice(builder.Length, RandomFileNameLength));
+ builder.Length += RandomFileNameLength;
+
+ string path = PathHelper.Normalize(ref builder);
+
+ bool directoryCreated = Interop.Kernel32.CreateDirectory(path, null);
+ if (!directoryCreated)
+ {
+ // in the off-chance that the directory already exists, try again
+ int error = Marshal.GetLastWin32Error();
+ if (error == Interop.Errors.ERROR_ALREADY_EXISTS)
+ {
+ builder.Length = initialTempPathLength;
+ attempts++;
+ continue;
+ }
+
+ throw Win32Marshal.GetExceptionForWin32Error(error, path);
+ }
+
+ builder.Dispose();
+ return path;
+ }
+
+ throw new IOException(SR.IO_MaxAttemptsReached);
+ }
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs
index 0087ecfef8f9bb..372b120a9eafe5 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs
@@ -6,6 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Enumeration;
+using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
namespace System.IO
@@ -52,6 +53,29 @@ public static DirectoryInfo CreateDirectory(string path)
public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode)
=> CreateDirectoryCore(path, unixCreateMode);
+ ///
+ /// Creates a uniquely-named, empty directory in the current user's temporary directory.
+ ///
+ /// An optional string to add to the beginning of the subdirectory name.
+ /// An object that represents the directory that was created.
+ /// contains a directory separator.
+ /// A new directory cannot be created.
+ public static unsafe DirectoryInfo CreateTempSubdirectory(string? prefix = null)
+ {
+ EnsureNoDirectorySeparators(prefix);
+
+ string path = CreateTempSubdirectoryCore(prefix);
+ return new DirectoryInfo(path, isNormalized: true);
+ }
+
+ private static void EnsureNoDirectorySeparators(string? value, [CallerArgumentExpression("value")] string? paramName = null)
+ {
+ if (value is not null && value.AsSpan().IndexOfAny(PathInternal.DirectorySeparators) >= 0)
+ {
+ throw new ArgumentException(SR.Argument_DirectorySeparatorInvalid, paramName);
+ }
+ }
+
// Tests if the given path refers to an existing DirectoryInfo on disk.
public static bool Exists([NotNullWhen(true)] string? path)
{
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs
index 899dc764f3e3d3..bc967e329950ca 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs
@@ -93,23 +93,32 @@ public static string GetTempPath()
path + PathInternal.DirectorySeparatorChar;
}
- public static string GetTempFileName()
+ public static unsafe string GetTempFileName()
{
- const string Suffix = ".tmp";
- const int SuffixByteLength = 4;
+ const int SuffixByteLength = 4; // ".tmp"
+ ReadOnlySpan fileTemplate = "tmpXXXXXX.tmp"u8;
// mkstemps takes a char* and overwrites the XXXXXX with six characters
// that'll result in a unique file name.
- string template = GetTempPath() + "tmpXXXXXX" + Suffix + "\0";
- byte[] name = Encoding.UTF8.GetBytes(template);
+ string tempPath = Path.GetTempPath();
+ int tempPathByteCount = Encoding.UTF8.GetByteCount(tempPath);
+ int totalByteCount = tempPathByteCount + fileTemplate.Length + 1;
+
+ Span path = totalByteCount <= 256 ? stackalloc byte[256].Slice(0, totalByteCount) : new byte[totalByteCount];
+ int pos = Encoding.UTF8.GetBytes(tempPath, path);
+ fileTemplate.CopyTo(path.Slice(pos));
+ path[^1] = 0;
// Create, open, and close the temp file.
- IntPtr fd = Interop.CheckIo(Interop.Sys.MksTemps(name, SuffixByteLength));
- Interop.Sys.Close(fd); // ignore any errors from close; nothing to do if cleanup isn't possible
+ fixed (byte* pPath = path)
+ {
+ IntPtr fd = Interop.CheckIo(Interop.Sys.MksTemps(pPath, SuffixByteLength));
+ Interop.Sys.Close(fd); // ignore any errors from close; nothing to do if cleanup isn't possible
+ }
- // 'name' is now the name of the file
- Debug.Assert(name[name.Length - 1] == '\0');
- return Encoding.UTF8.GetString(name, 0, name.Length - 1); // trim off the trailing '\0'
+ // 'path' is now the name of the file
+ Debug.Assert(path[^1] == 0);
+ return Encoding.UTF8.GetString(path.Slice(0, path.Length - 1)); // trim off the trailing '\0'
}
public static bool IsPathRooted([NotNullWhen(true)] string? path)
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs
index 690f2782389a24..f3aea073194aec 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs
@@ -151,7 +151,7 @@ public static string GetTempPath()
return path;
}
- private static void GetTempPath(ref ValueStringBuilder builder)
+ internal static void GetTempPath(ref ValueStringBuilder builder)
{
uint result;
while ((result = Interop.Kernel32.GetTempPathW(builder.Capacity, ref builder.GetPinnableReference())) > builder.Capacity)
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
index b6edb311eac4cf..75ffa1698e719d 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
@@ -818,7 +818,7 @@ private static unsafe string JoinInternal(ReadOnlySpan first, ReadOnlySpan
private static ReadOnlySpan Base32Char => "abcdefghijklmnopqrstuvwxyz012345"u8;
- private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int byteCount, Span chars)
+ internal static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int byteCount, Span chars)
{
// This method requires bytes of length 8 and chars of length 12.
Debug.Assert(bytes != null);
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs
index d91dd1c3a8dc75..89c2eb45ac852f 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Unix.cs
@@ -15,7 +15,7 @@ private static unsafe Dictionary> Initialize()
{
if (!Interop.Sys.InitializeTerminalAndSignalHandling())
{
- Interop.CheckIo(-1);
+ Interop.ThrowIOExceptionForLastError();
}
Interop.Sys.SetPosixSignalHandler(&OnPosixSignal);
@@ -44,7 +44,7 @@ private static PosixSignalRegistration Register(PosixSignal signal, Action
+ {
+ DirectoryInfo tempPathWithUnicode = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + "\u00F6"));
+ tempPathWithUnicode.Create();
+
+ string tempEnvVar = OperatingSystem.IsWindows() ? "TMP" : "TMPDIR";
+ Environment.SetEnvironmentVariable(tempEnvVar, tempPathWithUnicode.FullName);
+
+ try
+ {
+ string tmpFile = Path.GetTempFileName();
+ Assert.True(File.Exists(tmpFile));
+ Assert.Equal(tempPathWithUnicode.FullName, Path.GetDirectoryName(tmpFile));
+
+ Environment.SetEnvironmentVariable(tempEnvVar, tempPathWithUnicode.Parent.FullName);
+ }
+ finally
+ {
+ tempPathWithUnicode.Delete(recursive: true);
+ }
+ }).Dispose();
+ }
+
[Fact]
public void GetFullPath_InvalidArgs()
{
diff --git a/src/libraries/System.Runtime.Loader/tests/RefEmitLoadContext/RefEmitLoadContextTest.cs b/src/libraries/System.Runtime.Loader/tests/RefEmitLoadContext/RefEmitLoadContextTest.cs
index 1d0caac253140c..1863ad3767d1f1 100644
--- a/src/libraries/System.Runtime.Loader/tests/RefEmitLoadContext/RefEmitLoadContextTest.cs
+++ b/src/libraries/System.Runtime.Loader/tests/RefEmitLoadContext/RefEmitLoadContextTest.cs
@@ -38,10 +38,7 @@ private static void Init()
var assemblyFilename = "System.Runtime.Loader.Noop.Assembly.dll";
// Form the dynamic path that would not collide if another instance of this test is running.
- s_loadFromPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
-
- // Create the folder
- Directory.CreateDirectory(s_loadFromPath);
+ s_loadFromPath = Directory.CreateTempSubdirectory().FullName;
var targetPath = Path.Combine(s_loadFromPath, assemblyFilename);
diff --git a/src/libraries/System.Runtime.Loader/tests/ResourceAssemblyLoadContext.cs b/src/libraries/System.Runtime.Loader/tests/ResourceAssemblyLoadContext.cs
index a9fee0bef7acbb..84ad66d3433455 100644
--- a/src/libraries/System.Runtime.Loader/tests/ResourceAssemblyLoadContext.cs
+++ b/src/libraries/System.Runtime.Loader/tests/ResourceAssemblyLoadContext.cs
@@ -42,10 +42,7 @@ protected override Assembly Load(AssemblyName assemblyName)
// This custom load context will extract that resource and store it at the %temp% path at runtime.
// This prevents the corerun from adding the test assembly to the TPA list.
// Once loaded it is not possible to unload the assembly, therefore it cannot be deleted.
- var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
-
- // Create the folder since it will not exist already
- Directory.CreateDirectory(tempPath);
+ var tempPath = Directory.CreateTempSubdirectory().FullName;
string path = Path.Combine(tempPath, assembly);
using (FileStream output = File.OpenWrite(path))
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index f0425a2bf00c2e..94de712e795afe 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -9379,6 +9379,7 @@ public static partial class Directory
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")]
public static System.IO.DirectoryInfo CreateDirectory(string path, System.IO.UnixFileMode unixCreateMode) { throw null; }
public static System.IO.FileSystemInfo CreateSymbolicLink(string path, string pathToTarget) { throw null; }
+ public static System.IO.DirectoryInfo CreateTempSubdirectory(string? prefix = null) { throw null; }
public static void Delete(string path) { }
public static void Delete(string path, bool recursive) { }
public static System.Collections.Generic.IEnumerable EnumerateDirectories(string path) { throw null; }
diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c
index a6e406b4a54d2c..8357bcc66bc43c 100644
--- a/src/native/libs/System.Native/entrypoints.c
+++ b/src/native/libs/System.Native/entrypoints.c
@@ -86,6 +86,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_MkNod)
DllImportEntry(SystemNative_GetDeviceIdentifiers)
DllImportEntry(SystemNative_MkFifo)
+ DllImportEntry(SystemNative_MkdTemp)
DllImportEntry(SystemNative_MksTemps)
DllImportEntry(SystemNative_MMap)
DllImportEntry(SystemNative_MUnmap)
diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c
index b31163a01e80be..4642b55707564b 100644
--- a/src/native/libs/System.Native/pal_io.c
+++ b/src/native/libs/System.Native/pal_io.c
@@ -794,6 +794,13 @@ int32_t SystemNative_MkFifo(const char* pathName, uint32_t mode)
return result;
}
+char* SystemNative_MkdTemp(char* pathTemplate)
+{
+ char* result = NULL;
+ while ((result = mkdtemp(pathTemplate)) == NULL && errno == EINTR);
+ return result;
+}
+
intptr_t SystemNative_MksTemps(char* pathTemplate, int32_t suffixLength)
{
intptr_t result;
diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h
index ec3049f5b78ec4..09aef0c0541a8b 100644
--- a/src/native/libs/System.Native/pal_io.h
+++ b/src/native/libs/System.Native/pal_io.h
@@ -561,6 +561,14 @@ PALEXPORT int32_t SystemNative_MkNod(const char* pathName, uint32_t mode, uint32
*/
PALEXPORT int32_t SystemNative_MkFifo(const char* pathName, uint32_t mode);
+/**
+ * Creates a directory name that adheres to the specified template, creates the directory on disk with
+ * 0700 permissions, and returns the directory name.
+ *
+ * Returns a pointer to the modified template string on success; otherwise, returns NULL and errno is set.
+ */
+PALEXPORT char* SystemNative_MkdTemp(char* pathTemplate);
+
/**
* Creates a file name that adheres to the specified template, creates the file on disk with
* 0600 permissions, and returns an open r/w File Descriptor on the file.