mirror of
https://github.com/actions/runner.git
synced 2026-07-04 11:42:21 +08:00
Compare commits
4 Commits
dap-execut
...
fix/normal
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed0e5b75ee | ||
|
|
fffded93ac | ||
|
|
8307b8fe33 | ||
|
|
7585eb30aa |
@@ -64,6 +64,7 @@ namespace GitHub.Runner.Common
|
||||
private readonly List<ProductInfoHeaderValue> _userAgents = new() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
|
||||
private CancellationTokenSource _runnerShutdownTokenSource = new();
|
||||
private object _perfLock = new();
|
||||
private string _canonicalRootDirectory;
|
||||
private Tracing _trace;
|
||||
private Tracing _actionsHttpTrace;
|
||||
private Tracing _netcoreHttpTrace;
|
||||
@@ -391,7 +392,12 @@ namespace GitHub.Runner.Common
|
||||
break;
|
||||
|
||||
case WellKnownDirectory.Root:
|
||||
path = new DirectoryInfo(GetDirectory(WellKnownDirectory.Bin)).Parent.FullName;
|
||||
if (_canonicalRootDirectory == null)
|
||||
{
|
||||
_canonicalRootDirectory = PathUtil.GetCanonicalPath(
|
||||
new DirectoryInfo(GetDirectory(WellKnownDirectory.Bin)).Parent.FullName);
|
||||
}
|
||||
path = _canonicalRootDirectory;
|
||||
break;
|
||||
|
||||
case WellKnownDirectory.Temp:
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace GitHub.Runner.Sdk
|
||||
{
|
||||
@@ -6,8 +9,98 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
public static readonly string PathVariable = "Path";
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern SafeFileHandle CreateFile(
|
||||
string lpFileName,
|
||||
uint dwDesiredAccess,
|
||||
uint dwShareMode,
|
||||
System.IntPtr lpSecurityAttributes,
|
||||
uint dwCreationDisposition,
|
||||
uint dwFlagsAndAttributes,
|
||||
System.IntPtr hTemplateFile);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern uint GetFinalPathNameByHandle(
|
||||
SafeFileHandle hFile,
|
||||
[Out] StringBuilder lpszFilePath,
|
||||
uint cchFilePath,
|
||||
uint dwFlags);
|
||||
|
||||
private const uint FILE_READ_ATTRIBUTES = 0x80;
|
||||
private const uint FILE_SHARE_READ = 0x1;
|
||||
private const uint FILE_SHARE_WRITE = 0x2;
|
||||
private const uint FILE_SHARE_DELETE = 0x4;
|
||||
private const uint OPEN_EXISTING = 3;
|
||||
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
|
||||
private const uint VOLUME_NAME_DOS = 0x0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the NTFS canonical path for a directory, resolving drive letter
|
||||
/// and folder name casing to match what is stored on disk.
|
||||
/// On non-Windows platforms, returns the path unchanged.
|
||||
/// </summary>
|
||||
public static string GetCanonicalPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
using var handle = CreateFile(
|
||||
path,
|
||||
FILE_READ_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
System.IntPtr.Zero,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS,
|
||||
System.IntPtr.Zero);
|
||||
|
||||
if (handle.IsInvalid)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
var buffer = new StringBuilder(1024);
|
||||
var result = GetFinalPathNameByHandle(handle, buffer, (uint)buffer.Capacity, VOLUME_NAME_DOS);
|
||||
if (result == 0)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
// Retry with a larger buffer if the path was longer than expected
|
||||
if (result >= buffer.Capacity)
|
||||
{
|
||||
buffer = new StringBuilder((int)result + 1);
|
||||
result = GetFinalPathNameByHandle(handle, buffer, (uint)buffer.Capacity, VOLUME_NAME_DOS);
|
||||
if (result == 0 || result >= buffer.Capacity)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
var canonicalPath = buffer.ToString();
|
||||
|
||||
// Strip the \\?\UNC\ prefix and convert to standard UNC path
|
||||
if (canonicalPath.StartsWith(@"\\?\UNC\", System.StringComparison.Ordinal))
|
||||
{
|
||||
canonicalPath = @"\\" + canonicalPath.Substring(8);
|
||||
}
|
||||
// Strip the \\?\ prefix for local paths
|
||||
else if (canonicalPath.StartsWith(@"\\?\", System.StringComparison.Ordinal))
|
||||
{
|
||||
canonicalPath = canonicalPath.Substring(4);
|
||||
}
|
||||
|
||||
return canonicalPath;
|
||||
}
|
||||
#else
|
||||
public static readonly string PathVariable = "PATH";
|
||||
|
||||
public static string GetCanonicalPath(string path)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
#endif
|
||||
|
||||
public static string PrependPath(string path, string currentPath)
|
||||
|
||||
@@ -299,6 +299,52 @@ namespace GitHub.Runner.Common.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetDirectoryRootReturnsCachedValue()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
|
||||
// Call GetDirectory(Root) twice — should return the same reference
|
||||
var root1 = _hc.GetDirectory(WellKnownDirectory.Root);
|
||||
var root2 = _hc.GetDirectory(WellKnownDirectory.Root);
|
||||
|
||||
Assert.NotNull(root1);
|
||||
Assert.Equal(root1, root2);
|
||||
Assert.True(Directory.Exists(root1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetDirectoryDerivedPathsUseRootCasing()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
|
||||
var root = _hc.GetDirectory(WellKnownDirectory.Root);
|
||||
var diag = _hc.GetDirectory(WellKnownDirectory.Diag);
|
||||
var externals = _hc.GetDirectory(WellKnownDirectory.Externals);
|
||||
|
||||
// Diag and Externals should start with the same Root prefix
|
||||
Assert.StartsWith(root, diag);
|
||||
Assert.StartsWith(root, externals);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
private void Setup([CallerMemberName] string testName = "")
|
||||
{
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
|
||||
151
src/Test/L0/Util/PathUtilL0.cs
Normal file
151
src/Test/L0/Util/PathUtilL0.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using GitHub.Runner.Sdk;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Util
|
||||
{
|
||||
public sealed class PathUtilL0
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetCanonicalPath_ReturnsPath_WhenDirectoryDoesNotExist()
|
||||
{
|
||||
var fakePath = Path.Combine(Path.GetTempPath(), "nonexistent_" + Path.GetRandomFileName());
|
||||
var result = PathUtil.GetCanonicalPath(fakePath);
|
||||
Assert.Equal(fakePath, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetCanonicalPath_ReturnsPath_WhenNull()
|
||||
{
|
||||
Assert.Null(PathUtil.GetCanonicalPath(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetCanonicalPath_ReturnsEmpty_WhenEmpty()
|
||||
{
|
||||
Assert.Equal(string.Empty, PathUtil.GetCanonicalPath(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetCanonicalPath_ReturnsValidPath_ForExistingDirectory()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "pathutil_test_" + Path.GetRandomFileName());
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(tempDir);
|
||||
var result = PathUtil.GetCanonicalPath(tempDir);
|
||||
Assert.NotNull(result);
|
||||
Assert.True(Directory.Exists(result));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempDir))
|
||||
{
|
||||
Directory.Delete(tempDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetCanonicalPath_NormalizesDriveLetter_OnWindows()
|
||||
{
|
||||
var tempDir = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar);
|
||||
|
||||
// Skip if temp is a UNC path (no drive letter to normalize)
|
||||
if (tempDir.StartsWith(@"\\"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Force lowercase drive letter
|
||||
var lowerCased = char.ToLower(tempDir[0]) + tempDir.Substring(1);
|
||||
|
||||
var result = PathUtil.GetCanonicalPath(lowerCased);
|
||||
|
||||
// The canonical path should have an uppercase drive letter
|
||||
Assert.True(char.IsUpper(result[0]),
|
||||
$"Expected uppercase drive letter but got: {result}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetCanonicalPath_NormalizesFolderCasing_OnWindows()
|
||||
{
|
||||
// Create a directory with known casing, then query with wrong casing
|
||||
var basePath = Path.GetTempPath();
|
||||
if (basePath.StartsWith(@"\\"))
|
||||
{
|
||||
return; // Skip UNC
|
||||
}
|
||||
|
||||
var realName = "PathUtilTest_MiXeDcAsE_" + Path.GetRandomFileName();
|
||||
var realDir = Path.Combine(basePath, realName);
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(realDir);
|
||||
|
||||
// Query with all-lowercase version
|
||||
var wrongCased = Path.Combine(basePath, realName.ToLowerInvariant());
|
||||
|
||||
var result = PathUtil.GetCanonicalPath(wrongCased);
|
||||
|
||||
// The canonical result should contain the original mixed-case name
|
||||
Assert.Contains(realName, result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(realDir))
|
||||
{
|
||||
Directory.Delete(realDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetCanonicalPath_IsIdempotent_OnWindows()
|
||||
{
|
||||
// Calling GetCanonicalPath twice should return the same result
|
||||
var tempDir = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar);
|
||||
var first = PathUtil.GetCanonicalPath(tempDir);
|
||||
var second = PathUtil.GetCanonicalPath(first);
|
||||
Assert.Equal(first, second);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void GetCanonicalPath_ReturnsSameResult_RegardlessOfInputCasing_OnWindows()
|
||||
{
|
||||
var tempDir = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar);
|
||||
if (tempDir.StartsWith(@"\\"))
|
||||
{
|
||||
return; // Skip UNC
|
||||
}
|
||||
|
||||
var upper = tempDir.ToUpperInvariant();
|
||||
var lower = tempDir.ToLowerInvariant();
|
||||
|
||||
var resultUpper = PathUtil.GetCanonicalPath(upper);
|
||||
var resultLower = PathUtil.GetCanonicalPath(lower);
|
||||
|
||||
// Both should resolve to the same canonical path
|
||||
Assert.Equal(resultUpper, resultLower);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user