diff --git a/src/Build.OM.UnitTests/Definition/Project_Tests.cs b/src/Build.OM.UnitTests/Definition/Project_Tests.cs index 4d856fb153d..beb78eec2ae 100644 --- a/src/Build.OM.UnitTests/Definition/Project_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/Project_Tests.cs @@ -654,9 +654,7 @@ public void TransformsUseCorrectDirectory_Basic() project.ReevaluateIfNecessary(); project.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe( - NativeMethodsShared.IsWindows - ? Path.Combine(Path.GetTempPath(), @"obj\i386\foo.dll") - : Path.Combine(Path.GetTempPath(), @"obj/i386/foo.dll")); + Path.Combine(FileUtilities.TempFileDirectory, "obj", "i386", "foo.dll")); } finally { @@ -721,8 +719,8 @@ public void TransformsUseCorrectDirectory_DirectoryTransform() Project project = new Project(xml); ProjectInstance projectInstance = new ProjectInstance(xml); - project.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(Path.GetTempPath(), "obj", "i386").Substring(RootPrefixLength) + Path.DirectorySeparatorChar); - projectInstance.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(Path.GetTempPath(), "obj", "i386").Substring(RootPrefixLength) + Path.DirectorySeparatorChar); + project.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(FileUtilities.TempFileDirectory, "obj", "i386").Substring(RootPrefixLength) + Path.DirectorySeparatorChar); + projectInstance.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(FileUtilities.TempFileDirectory, "obj", "i386").Substring(RootPrefixLength) + Path.DirectorySeparatorChar); } finally { @@ -756,8 +754,8 @@ public void TransformsUseCorrectDirectory_DirectoryItemFunction() Project project = new Project(xml); ProjectInstance projectInstance = new ProjectInstance(xml); - project.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(Path.GetTempPath(), "obj", "i386").Substring(RootPrefixLength) + Path.DirectorySeparatorChar); - projectInstance.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(Path.GetTempPath(), "obj", "i386").Substring(RootPrefixLength) + Path.DirectorySeparatorChar); + project.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(FileUtilities.TempFileDirectory, "obj", "i386").Substring(RootPrefixLength) + Path.DirectorySeparatorChar); + projectInstance.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(FileUtilities.TempFileDirectory, "obj", "i386").Substring(RootPrefixLength) + Path.DirectorySeparatorChar); } finally { @@ -794,8 +792,8 @@ public void TransformsUseCorrectDirectory_DirectoryNameItemFunction() ProjectInstance projectInstance = new ProjectInstance(xml); // Should be the full path to the directory - project.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(Path.GetTempPath() /* remove c:\ */, "obj" + Path.DirectorySeparatorChar + "i386")); - projectInstance.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(Path.GetTempPath() /* remove c:\ */, "obj" + Path.DirectorySeparatorChar + "i386")); + project.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(FileUtilities.TempFileDirectory /* remove c:\ */, "obj" + Path.DirectorySeparatorChar + "i386")); + projectInstance.GetItems("BuiltProjectOutputGroupKeyOutput").First().EvaluatedInclude.ShouldBe(Path.Combine(FileUtilities.TempFileDirectory /* remove c:\ */, "obj" + Path.DirectorySeparatorChar + "i386")); } finally { diff --git a/src/Build.UnitTests/BackEnd/BuildRequestConfiguration_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequestConfiguration_Tests.cs index 7d42cfe8206..12fbe5d15d9 100644 --- a/src/Build.UnitTests/BackEnd/BuildRequestConfiguration_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildRequestConfiguration_Tests.cs @@ -476,6 +476,7 @@ public void TestCache2() Environment.SetEnvironmentVariable("TEMP", problematicTmpPath); FileUtilities.ClearCacheDirectoryPath(); + FileUtilities.ClearTempFileDirectory(); string cacheFilePath = configuration.GetCacheFile(); Assert.StartsWith(problematicTmpPath, cacheFilePath); } @@ -484,6 +485,7 @@ public void TestCache2() Environment.SetEnvironmentVariable("TMP", originalTmp); Environment.SetEnvironmentVariable("TEMP", originalTemp); FileUtilities.ClearCacheDirectoryPath(); + FileUtilities.ClearTempFileDirectory(); } } diff --git a/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs b/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs index 351d86a0d6a..0419a840a6c 100644 --- a/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs +++ b/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs @@ -24,7 +24,7 @@ public void DumpExceptionToFileShouldWriteInTempPathByDefault() try { ExceptionHandling.DumpExceptionToFile(new Exception("hello world")); - exceptionFiles = Directory.GetFiles(Path.GetTempPath(), "MSBuild_*failure.txt"); + exceptionFiles = Directory.GetFiles(FileUtilities.TempFileDirectory, "MSBuild_*failure.txt"); } finally { diff --git a/src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs b/src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs index a90afeabc38..f2abb27be9c 100644 --- a/src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs @@ -990,7 +990,7 @@ private void SimpleSymlinkInputCheck(DateTime symlinkWriteTime, DateTime targetW _testOutputHelper.WriteLine($"Created input file {inputTarget}"); File.SetLastWriteTime(inputTarget, targetWriteTime); - inputSymlink = FileUtilities.GetTemporaryFile(null, ".linkin", createFile: false); + inputSymlink = FileUtilities.GetTemporaryFile(null, null, ".linkin", createFile: false); if (!CreateSymbolicLink(inputSymlink, inputTarget, 0)) { diff --git a/src/Build.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.UnitTests/Construction/SolutionFile_Tests.cs index d2ab15a93eb..16c1c196883 100644 --- a/src/Build.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFile_Tests.cs @@ -141,7 +141,7 @@ public void ParseFirstProjectLine_InvalidProject() [Fact] public void ParseEtpProject() { - string proj1Path = Path.Combine(Path.GetTempPath(), "someproj.etp"); + string proj1Path = Path.Combine(FileUtilities.TempFileDirectory, "someproj.etp"); try { // Create the first .etp project file @@ -192,8 +192,8 @@ public void ParseEtpProject() [Fact] public void CanBeMSBuildFile() { - string proj1Path = Path.Combine(Path.GetTempPath(), "someproj.etp"); - string proj2Path = Path.Combine(Path.GetTempPath(), "someproja.proj"); + string proj1Path = Path.Combine(FileUtilities.TempFileDirectory, "someproj.etp"); + string proj2Path = Path.Combine(FileUtilities.TempFileDirectory, "someproja.proj"); try { // Create the first .etp project file @@ -317,8 +317,8 @@ public void CanBeMSBuildFileRejectsMSBuildLikeFiles() [Fact] public void ParseNestedEtpProjectSingleLevel() { - string proj1Path = Path.Combine(Path.GetTempPath(), "someproj.etp"); - string proj2Path = Path.Combine(Path.GetTempPath(), "someproj2.etp"); + string proj1Path = Path.Combine(FileUtilities.TempFileDirectory, "someproj.etp"); + string proj2Path = Path.Combine(FileUtilities.TempFileDirectory, "someproj2.etp"); try { // Create the first .etp project file @@ -513,9 +513,9 @@ public void TestVSAndSolutionVersionParsing() [Trait("Category", "netcore-linux-failing")] public void ParseNestedEtpProjectMultipleLevel() { - string proj1Path = Path.Combine(Path.GetTempPath(), "someproj.etp"); - string proj2Path = Path.Combine(Path.GetTempPath(), "someproj2.etp"); - string proj3Path = Path.Combine(Path.GetTempPath(), "ETPProjUpgradeTest", "someproj3.etp"); + string proj1Path = Path.Combine(FileUtilities.TempFileDirectory, "someproj.etp"); + string proj2Path = Path.Combine(FileUtilities.TempFileDirectory, "someproj2.etp"); + string proj3Path = Path.Combine(FileUtilities.TempFileDirectory, "ETPProjUpgradeTest", "someproj3.etp"); try { // Create the first .etp project file @@ -567,7 +567,7 @@ public void ParseNestedEtpProjectMultipleLevel() "; // Create the directory for the third project - Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "ETPProjUpgradeTest")); + Directory.CreateDirectory(Path.Combine(FileUtilities.TempFileDirectory, "ETPProjUpgradeTest")); File.WriteAllText(proj3Path, etpProjContent); // Create the SolutionFile object @@ -602,7 +602,7 @@ public void ParseNestedEtpProjectMultipleLevel() [Fact] public void MalformedEtpProjFile() { - string proj1Path = Path.Combine(Path.GetTempPath(), "someproj.etp"); + string proj1Path = Path.Combine(FileUtilities.TempFileDirectory, "someproj.etp"); try { // Create the .etp project file diff --git a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs index 780b83ffa0b..6c726a5476d 100644 --- a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs @@ -850,7 +850,7 @@ public void SolutionConfigurationWithDependencies() Debug|AnyCPU Debug|AnyCPU Debug|AnyCPU -".Replace("`", "\"").Replace("##temp##", Path.GetTempPath()); +".Replace("`", "\"").Replace("##temp##", FileUtilities.TempFileDirectory); Helpers.VerifyAssertLineByLine(expected, solutionConfigurationContents); } @@ -1090,14 +1090,14 @@ public void TestAddPropertyGroupForSolutionConfiguration() msbuildProject.ReevaluateIfNecessary(); string solutionConfigurationContents = msbuildProject.GetPropertyValue("CurrentSolutionConfigurationContents"); - string tempProjectPath = Path.Combine(Path.GetTempPath(), "ClassLibrary1", "ClassLibrary1.csproj"); + string tempProjectPath = Path.Combine(FileUtilities.TempFileDirectory, "ClassLibrary1", "ClassLibrary1.csproj"); Assert.Contains("{6185CC21-BE89-448A-B3C0-D1C27112E595}", solutionConfigurationContents); tempProjectPath = Path.GetFullPath(tempProjectPath); Assert.True(solutionConfigurationContents.IndexOf(tempProjectPath, StringComparison.OrdinalIgnoreCase) > 0); Assert.Contains("CSConfig1|AnyCPU", solutionConfigurationContents); - tempProjectPath = Path.Combine(Path.GetTempPath(), "MainApp", "MainApp.vcxproj"); + tempProjectPath = Path.Combine(FileUtilities.TempFileDirectory, "MainApp", "MainApp.vcxproj"); tempProjectPath = Path.GetFullPath(tempProjectPath); Assert.Contains("{A6F99D27-47B9-4EA4-BFC9-25157CBDC281}", solutionConfigurationContents); Assert.True(solutionConfigurationContents.IndexOf(tempProjectPath, StringComparison.OrdinalIgnoreCase) > 0); diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs index e77a47e5ef4..8501b7297f9 100644 --- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs @@ -3052,7 +3052,7 @@ public void PropertyFunctionStaticMethodEnumArgument() [Fact] public void PropertyFunctionStaticMethodDirectoryNameOfFileAbove() { - string tempPath = Path.GetTempPath(); + string tempPath = FileUtilities.TempFileDirectory; string tempFile = Path.GetFileName(FileUtilities.GetTemporaryFile()); try @@ -3090,7 +3090,7 @@ public void PropertyFunctionStaticMethodGetPathOfFileAbove() // MockElementLocation mockElementLocation = new MockElementLocation(Path.Combine(ObjectModelHelpers.TempProjectDir, "one", "two", "three", "four", "five", Path.GetRandomFileName())); - string fileToFind = FileUtilities.GetTemporaryFile(ObjectModelHelpers.TempProjectDir, ".tmp"); + string fileToFind = FileUtilities.GetTemporaryFile(ObjectModelHelpers.TempProjectDir, null, ".tmp"); try { diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index f683529b74d..a188f318b64 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -409,6 +409,14 @@ public DeferredBuildMessage(string text, MessageImportance importance) /// Thrown if a build is already in progress. public void BeginBuild(BuildParameters parameters, IEnumerable deferredBuildMessages) { + // TEMP can be modified from the environment. Most of Traits is lasts for the duration of the process (with a manual reset for tests) + // and environment variables we use as properties are stored in a dictionary at the beginning of the build, so they also cannot be + // changed during a build. Some of our older stuff uses live environment variable checks. The TEMP directory previously used a live + // environment variable check, but it now uses a cached value. Nevertheless, we should support changing it between builds, so reset + // it here in case the user is using Visual Studio or the MSBuild server, as those each last for multiple builds without changing + // BuildManager. + FileUtilities.ClearTempFileDirectory(); + // deferredBuildMessages cannot be an optional parameter on a single BeginBuild method because it would break binary compatibility. _deferredBuildMessages = deferredBuildMessages; BeginBuild(parameters); diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index 3bb58aa36ab..d635c661da9 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -127,7 +127,7 @@ internal BuildRequestEngine() if (String.IsNullOrEmpty(_debugDumpPath)) { - _debugDumpPath = Path.GetTempPath(); + _debugDumpPath = FileUtilities.TempFileDirectory; } _status = BuildRequestEngineStatus.Uninitialized; diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 44f487998b8..f166c13d9fa 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -228,7 +228,7 @@ public Scheduler() if (String.IsNullOrEmpty(_debugDumpPath)) { - _debugDumpPath = Path.GetTempPath(); + _debugDumpPath = FileUtilities.TempFileDirectory; } Reset(); diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs index d0c29652824..60d986402ca 100644 --- a/src/Framework/NativeMethods.cs +++ b/src/Framework/NativeMethods.cs @@ -655,6 +655,7 @@ internal static bool IsUnixLike /// /// Gets a flag indicating if we are running under Linux /// + [SupportedOSPlatformGuard("linux")] internal static bool IsLinux { #if CLR2COMPATIBILITY @@ -1470,9 +1471,16 @@ internal static void VerifyThrowWin32Result(int result) } } -#endregion + #endregion + + #region PInvoke + [SupportedOSPlatform("linux")] + [DllImport("libc", SetLastError = true)] + internal static extern int chmod(string pathname, int mode); -#region PInvoke + [SupportedOSPlatform("linux")] + [DllImport("libc", SetLastError = true)] + internal static extern int mkdir(string path, int mode); /// /// Gets the current OEM code page which is used by console apps diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index 9ec5f525074..629d9141a93 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -792,7 +792,7 @@ private NodeEngineShutdownReason HandleShutdown() if (_debugCommunications) { - using (StreamWriter writer = File.CreateText(String.Format(CultureInfo.CurrentCulture, Path.Combine(Path.GetTempPath(), @"MSBuild_NodeShutdown_{0}.txt"), Process.GetCurrentProcess().Id))) + using (StreamWriter writer = File.CreateText(String.Format(CultureInfo.CurrentCulture, Path.Combine(FileUtilities.TempFileDirectory, @"MSBuild_NodeShutdown_{0}.txt"), Process.GetCurrentProcess().Id))) { writer.WriteLine("Node shutting down with reason {0}.", _shutdownReason); } diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs index f8106579fb7..0aa08501488 100644 --- a/src/Shared/CommunicationsUtilities.cs +++ b/src/Shared/CommunicationsUtilities.cs @@ -693,7 +693,7 @@ internal static void Trace(int nodeId, string format, params object[] args) if (String.IsNullOrEmpty(s_debugDumpPath)) { - s_debugDumpPath = Path.GetTempPath(); + s_debugDumpPath = FileUtilities.TempFileDirectory; } else { diff --git a/src/Shared/Debugging/DebugUtils.cs b/src/Shared/Debugging/DebugUtils.cs index 74a3a7b9e79..3ae6cf16891 100644 --- a/src/Shared/Debugging/DebugUtils.cs +++ b/src/Shared/Debugging/DebugUtils.cs @@ -38,7 +38,7 @@ static DebugUtils() } else { - debugDirectory = Path.Combine(Path.GetTempPath(), "MSBuild_Logs"); + debugDirectory = Path.Combine(FileUtilities.TempFileDirectory, "MSBuild_Logs"); } // Out of proc nodes do not know the startup directory so set the environment variable for them. diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs index 2d91a466fbf..3f0b910b267 100644 --- a/src/Shared/ExceptionHandling.cs +++ b/src/Shared/ExceptionHandling.cs @@ -54,7 +54,7 @@ private static string GetDebugDumpPath() return !string.IsNullOrEmpty(debugPath) ? debugPath - : Path.GetTempPath(); + : FileUtilities.TempFileDirectory; } /// diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index da24b87b134..3861a120d79 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -45,6 +45,10 @@ internal static partial class FileUtilities /// internal static string cacheDirectory = null; +#if CLR2COMPATIBILITY + internal static string TempFileDirectory => Path.GetTempPath(); +#endif + /// /// FOR UNIT TESTS ONLY /// Clear out the static variable used for the cache directory so that tests that @@ -122,7 +126,7 @@ internal static string GetCacheDirectory() { if (cacheDirectory == null) { - cacheDirectory = Path.Combine(Path.GetTempPath(), String.Format(CultureInfo.CurrentUICulture, "MSBuild{0}-{1}", Process.GetCurrentProcess().Id, AppDomain.CurrentDomain.Id)); + cacheDirectory = Path.Combine(TempFileDirectory, String.Format(CultureInfo.CurrentUICulture, "MSBuild{0}-{1}", Process.GetCurrentProcess().Id, AppDomain.CurrentDomain.Id)); } return cacheDirectory; diff --git a/src/Shared/NamedPipeUtil.cs b/src/Shared/NamedPipeUtil.cs index dfc76317e84..4927b87103d 100644 --- a/src/Shared/NamedPipeUtil.cs +++ b/src/Shared/NamedPipeUtil.cs @@ -30,7 +30,13 @@ internal static string GetPlatformSpecificPipeName(string pipeName) // can be quite long, leaving very little room for the actual pipe name. Fortunately, // '/tmp' is mandated by POSIX to always be a valid temp directory, so we can use that // instead. +#if !CLR2COMPATIBILITY return Path.Combine("/tmp", pipeName); +#else + // We should never get here. This would be a net35 task host running on unix. + ErrorUtilities.ThrowInternalError("Task host used on unix in retrieving the pipe name."); + return string.Empty; +#endif } else { diff --git a/src/Shared/NodeEndpointOutOfProcBase.cs b/src/Shared/NodeEndpointOutOfProcBase.cs index 4c5a3357063..aefd4aaebb2 100644 --- a/src/Shared/NodeEndpointOutOfProcBase.cs +++ b/src/Shared/NodeEndpointOutOfProcBase.cs @@ -232,7 +232,11 @@ internal void InternalConstruct(string pipeName = null) PipeDirection.InOut, 1, // Only allow one connection at a time. PipeTransmissionMode.Byte, - PipeOptions.Asynchronous | PipeOptions.WriteThrough, + PipeOptions.Asynchronous | PipeOptions.WriteThrough +#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY + | PipeOptions.CurrentUserOnly +#endif + , PipeBufferSize, // Default input buffer PipeBufferSize, // Default output buffer security, @@ -248,7 +252,11 @@ internal void InternalConstruct(string pipeName = null) PipeDirection.InOut, 1, // Only allow one connection at a time. PipeTransmissionMode.Byte, - PipeOptions.Asynchronous | PipeOptions.WriteThrough, + PipeOptions.Asynchronous | PipeOptions.WriteThrough +#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY + | PipeOptions.CurrentUserOnly +#endif + , PipeBufferSize, // Default input buffer PipeBufferSize // Default output buffer ); diff --git a/src/Shared/TempFileUtilities.cs b/src/Shared/TempFileUtilities.cs index f3bffb9d425..c0583acb483 100644 --- a/src/Shared/TempFileUtilities.cs +++ b/src/Shared/TempFileUtilities.cs @@ -16,6 +16,57 @@ namespace Microsoft.Build.Shared /// internal static partial class FileUtilities { + // For the current user, these correspond to read, write, and execute permissions. + // Lower order bits correspond to the same for "group" or "other" users. + private const int userRWX = 0x100 | 0x80 | 0x40; + private static string tempFileDirectory = null; + internal static string TempFileDirectory + { + get + { + return tempFileDirectory ??= CreateFolderUnderTemp(); + } + } + + internal static void ClearTempFileDirectory() + { + tempFileDirectory = null; + } + + // For all native calls, directly check their return values to prevent bad actors from getting in between checking if a directory exists and returning it. + private static string CreateFolderUnderTemp() + { + string basePath = Path.Combine(Path.GetTempPath(), $"MSBuildTemp{Environment.UserName}"); + + if (NativeMethodsShared.IsLinux && NativeMethodsShared.mkdir(basePath, userRWX) != 0) + { + if (NativeMethodsShared.chmod(basePath, userRWX) == 0) + { + // Current user owns this file; we can read and write to it. It is reasonable here to assume it was created properly by MSBuild and can be used + // for temporary files. + } + else + { + // Another user created a folder pretending to be us! Find a folder we can actually use. + int extraBits = 0; + string pathToCheck = basePath + extraBits; + while (NativeMethodsShared.mkdir(pathToCheck, userRWX) != 0 && NativeMethodsShared.chmod(pathToCheck, userRWX) != 0) + { + extraBits++; + pathToCheck = basePath + extraBits; + } + + basePath = pathToCheck; + } + } + else + { + Directory.CreateDirectory(basePath); + } + + return FileUtilities.EnsureTrailingSlash(basePath); + } + /// /// Generates a unique directory name in the temporary folder. /// Caller must delete when finished. @@ -24,7 +75,7 @@ internal static partial class FileUtilities /// internal static string GetTemporaryDirectory(bool createDirectory = true, string subfolder = null) { - string temporaryDirectory = Path.Combine(Path.GetTempPath(), "Temporary" + Guid.NewGuid().ToString("N"), subfolder ?? string.Empty); + string temporaryDirectory = Path.Combine(TempFileDirectory, "Temporary" + Guid.NewGuid().ToString("N"), subfolder ?? string.Empty); if (createDirectory) { @@ -43,7 +94,7 @@ internal static string GetTemporaryDirectory(bool createDirectory = true, string /// internal static string GetTemporaryFileName(string extension) { - return GetTemporaryFile(null, extension, false); + return GetTemporaryFile(null, null, extension, false); } /// @@ -57,6 +108,16 @@ internal static string GetTemporaryFile() return GetTemporaryFile(".tmp"); } + /// + /// Generates a unique temporary file name with a given extension in the temporary folder. + /// File is guaranteed to be unique. + /// Caller must delete it when finished. + /// + internal static string GetTemporaryFile(string fileName, string extension, bool createFile) + { + return GetTemporaryFile(null, fileName, extension, createFile); + } + /// /// Generates a unique temporary file name with a given extension in the temporary folder. /// File is guaranteed to be unique. @@ -66,7 +127,7 @@ internal static string GetTemporaryFile() /// internal static string GetTemporaryFile(string extension) { - return GetTemporaryFile(null, extension); + return GetTemporaryFile(null, null, extension); } /// @@ -77,23 +138,33 @@ internal static string GetTemporaryFile(string extension) /// Caller must delete it when finished. /// May throw IOException. /// - internal static string GetTemporaryFile(string directory, string extension, bool createFile = true) + internal static string GetTemporaryFile(string directory, string fileName, string extension, bool createFile = true) { ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(directory, nameof(directory)); - ErrorUtilities.VerifyThrowArgumentLength(extension, nameof(extension)); - - if (extension[0] != '.') - { - extension = '.' + extension; - } try { - directory ??= Path.GetTempPath(); + directory ??= TempFileDirectory; + + // If the extension needs a dot prepended, do so. + if (extension is null) + { + extension = string.Empty; + } + else if (extension.Length > 0 && extension[0] != '.') + { + extension = '.' + extension; + } + + // If the fileName is null, use tmp{Guid}; otherwise use fileName. + if (string.IsNullOrEmpty(fileName)) + { + fileName = $"tmp{Guid.NewGuid():N}"; + } Directory.CreateDirectory(directory); - string file = Path.Combine(directory, $"tmp{Guid.NewGuid():N}{extension}"); + string file = Path.Combine(directory, $"{fileName}{extension}"); ErrorUtilities.VerifyThrow(!FileSystems.Default.FileExists(file), "Guid should be unique"); @@ -131,11 +202,15 @@ public class TempWorkingDirectory : IDisposable { public string Path { get; } - public TempWorkingDirectory(string sourcePath, [CallerMemberName] string name = null) + public TempWorkingDirectory(string sourcePath, +#if !CLR2COMPATIBILITY + [CallerMemberName] +#endif + string name = null) { Path = name == null ? GetTemporaryDirectory() - : System.IO.Path.Combine(System.IO.Path.GetTempPath(), name); + : System.IO.Path.Combine(TempFileDirectory, name); if (FileSystems.Default.DirectoryExists(Path)) { diff --git a/src/Shared/UnitTests/FileUtilities_Tests.cs b/src/Shared/UnitTests/FileUtilities_Tests.cs index 3f9c53f3a68..7e87e2217af 100644 --- a/src/Shared/UnitTests/FileUtilities_Tests.cs +++ b/src/Shared/UnitTests/FileUtilities_Tests.cs @@ -851,7 +851,7 @@ public void GenerateTempFileNameWithDirectoryAndExtension() try { - path = FileUtilities.GetTemporaryFile(directory, ".bat"); + path = FileUtilities.GetTemporaryFile(directory, null, ".bat"); Assert.EndsWith(".bat", path); Assert.True(File.Exists(path)); @@ -902,18 +902,6 @@ public void GenerateTempBatchFileWithBadExtension() ); } /// - /// No extension is given - /// - [Fact] - public void GenerateTempBatchFileWithEmptyExtension() - { - Assert.Throws(() => - { - FileUtilities.GetTemporaryFile(String.Empty); - } - ); - } - /// /// Directory is invalid /// [Fact] @@ -924,7 +912,7 @@ public void GenerateTempBatchFileWithBadDirectory() { Assert.Throws(() => { - FileUtilities.GetTemporaryFile("|", ".tmp"); + FileUtilities.GetTemporaryFile("|", null, ".tmp"); } ); } diff --git a/src/Shared/UnitTests/TestEnvironment.cs b/src/Shared/UnitTests/TestEnvironment.cs index c97b575d88c..51ea0482d88 100644 --- a/src/Shared/UnitTests/TestEnvironment.cs +++ b/src/Shared/UnitTests/TestEnvironment.cs @@ -594,14 +594,14 @@ public TransientTestFile(string extension, bool createFile, bool expectedAsOutpu { _createFile = createFile; _expectedAsOutput = expectedAsOutput; - Path = FileUtilities.GetTemporaryFile(null, extension, createFile); + Path = FileUtilities.GetTemporaryFile(null, null, extension, createFile); } public TransientTestFile(string rootPath, string extension, bool createFile, bool expectedAsOutput) { _createFile = createFile; _expectedAsOutput = expectedAsOutput; - Path = FileUtilities.GetTemporaryFile(rootPath, extension, createFile); + Path = FileUtilities.GetTemporaryFile(rootPath, null, extension, createFile); } public TransientTestFile(string rootPath, string fileName, string contents = null) diff --git a/src/Tasks.UnitTests/Copy_Tests.cs b/src/Tasks.UnitTests/Copy_Tests.cs index 12705101c88..293fec51354 100644 --- a/src/Tasks.UnitTests/Copy_Tests.cs +++ b/src/Tasks.UnitTests/Copy_Tests.cs @@ -537,8 +537,8 @@ public void DoCopyOverCopiedFile(bool skipUnchangedFiles) { using (var env = TestEnvironment.Create()) { - var sourceFile = FileUtilities.GetTemporaryFile(env.DefaultTestDirectory.Path, "src", false); - var destinationFile = FileUtilities.GetTemporaryFile(env.DefaultTestDirectory.Path, "dst", false); + var sourceFile = FileUtilities.GetTemporaryFile(env.DefaultTestDirectory.Path, null, "src", false); + var destinationFile = FileUtilities.GetTemporaryFile(env.DefaultTestDirectory.Path, null, "dst", false); File.WriteAllText(sourceFile, "This is a source temp file."); diff --git a/src/Tasks/CodeTaskFactory.cs b/src/Tasks/CodeTaskFactory.cs index 77c8c929879..e3fd34e9fc6 100644 --- a/src/Tasks/CodeTaskFactory.cs +++ b/src/Tasks/CodeTaskFactory.cs @@ -811,19 +811,13 @@ private Assembly CompileInMemoryAssembly() var fullSpec = new FullTaskSpecification(finalReferencedAssemblies, fullCode); if (!s_compiledTaskCache.TryGetValue(fullSpec, out Assembly existingAssembly)) { - // Invokes compilation. - - // Note: CompileAssemblyFromSource uses Path.GetTempPath() directory, but will not create it. In some cases - // this will throw inside CompileAssemblyFromSource. To work around this, ensure the temp directory exists. - // See: https://github.com/dotnet/msbuild/issues/328 - Directory.CreateDirectory(Path.GetTempPath()); - + // Invokes compilation. CompilerResults compilerResults = provider.CompileAssemblyFromSource(compilerParameters, fullCode); string outputPath = null; if (compilerResults.Errors.Count > 0 || Environment.GetEnvironmentVariable("MSBUILDLOGCODETASKFACTORYOUTPUT") != null) { - string tempDirectory = Path.GetTempPath(); + string tempDirectory = FileUtilities.TempFileDirectory; string fileName = Guid.NewGuid().ToString() + ".txt"; outputPath = Path.Combine(tempDirectory, fileName); File.WriteAllText(outputPath, fullCode); diff --git a/src/Tasks/GetSDKReferenceFiles.cs b/src/Tasks/GetSDKReferenceFiles.cs index 1af05538af3..64afe2300aa 100644 --- a/src/Tasks/GetSDKReferenceFiles.cs +++ b/src/Tasks/GetSDKReferenceFiles.cs @@ -81,7 +81,7 @@ public class GetSDKReferenceFiles : TaskExtension /// /// Folder where the cache files are written to /// - private string _cacheFilePath = Path.GetTempPath(); + private string _cacheFilePath = FileUtilities.TempFileDirectory; #region Properties diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs index f9fbcadfcae..abeba2b2791 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs @@ -677,8 +677,8 @@ private bool TryCompileInMemoryAssembly(IBuildEngine buildEngine, RoslynCodeTask // The source code cannot actually be compiled "in memory" so instead the source code is written to disk in // the temp folder as well as the assembly. After compilation, the source code and assembly are deleted. - string sourceCodePath = Path.GetTempFileName(); - string assemblyPath = Path.Combine(Path.GetTempPath(), $"{Path.GetRandomFileName()}.dll"); + string sourceCodePath = FileUtilities.GetTemporaryFileName(".tmp"); + string assemblyPath = FileUtilities.GetTemporaryFileName(".dll"); // Delete the code file unless compilation failed or the environment variable MSBUILDLOGCODETASKFACTORYOUTPUT // is set (which allows for debugging problems) diff --git a/src/Tasks/TlbReference.cs b/src/Tasks/TlbReference.cs index 7350cee5080..f08ae5f4d27 100644 --- a/src/Tasks/TlbReference.cs +++ b/src/Tasks/TlbReference.cs @@ -75,7 +75,7 @@ internal TlbReference(TaskLoggingHelper taskLoggingHelper, bool silent, IComRefe /// /// directory we should write the wrapper to /// - protected override string OutputDirectory => (HasTemporaryWrapper) ? Path.GetTempPath() : base.OutputDirectory; + protected override string OutputDirectory => (HasTemporaryWrapper) ? FileUtilities.TempFileDirectory : base.OutputDirectory; private readonly bool _noClassMembers; private readonly string _targetProcessorArchitecture; diff --git a/src/Tasks/WriteCodeFragment.cs b/src/Tasks/WriteCodeFragment.cs index 26325de8241..5088a0ff87e 100644 --- a/src/Tasks/WriteCodeFragment.cs +++ b/src/Tasks/WriteCodeFragment.cs @@ -109,7 +109,7 @@ public override bool Execute() OutputFile = new TaskItem(Path.Combine(OutputDirectory.ItemSpec, OutputFile.ItemSpec)); } - OutputFile ??= new TaskItem(FileUtilities.GetTemporaryFile(OutputDirectory.ItemSpec, extension)); + OutputFile ??= new TaskItem(FileUtilities.GetTemporaryFile(OutputDirectory.ItemSpec, null, extension)); File.WriteAllText(OutputFile.ItemSpec, code); // Overwrites file if it already exists (and can be overwritten) } diff --git a/src/Utilities/TrackedDependencies/FileTracker.cs b/src/Utilities/TrackedDependencies/FileTracker.cs index 7eb6306f4a1..a6fa201acc7 100644 --- a/src/Utilities/TrackedDependencies/FileTracker.cs +++ b/src/Utilities/TrackedDependencies/FileTracker.cs @@ -73,7 +73,7 @@ public static class FileTracker #region Static Member Data // The default path to temp, used to create explicitly short and long paths - private static readonly string s_tempPath = Path.GetTempPath(); + private static readonly string s_tempPath = FileUtilities.TempFileDirectory; // The short path to temp private static readonly string s_tempShortPath = FileUtilities.EnsureTrailingSlash(NativeMethodsShared.GetShortFilePath(s_tempPath).ToUpperInvariant());