This commit is contained in:
DoctorKrolic 2025-07-30 22:34:32 +08:00 committed by GitHub
commit 5f03ba8891
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 111 additions and 19 deletions

View File

@ -7,6 +7,8 @@ internal static partial class Interop
{
internal static partial class FileAttributes
{
internal const int INVALID_FILE_ATTRIBUTES = -1;
internal const int FILE_ATTRIBUTE_NORMAL = 0x00000080;
internal const int FILE_ATTRIBUTE_READONLY = 0x00000001;
internal const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010;

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
@ -25,7 +26,7 @@ namespace System.IO
return
(lastError == 0) &&
(data.dwFileAttributes != -1) &&
(data.dwFileAttributes != Interop.Kernel32.FileAttributes.INVALID_FILE_ATTRIBUTES) &&
((data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) != 0);
}
@ -36,7 +37,7 @@ namespace System.IO
return
(errorCode == 0) &&
(data.dwFileAttributes != -1) &&
(data.dwFileAttributes != Interop.Kernel32.FileAttributes.INVALID_FILE_ATTRIBUTES) &&
((data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0);
}
@ -56,7 +57,14 @@ namespace System.IO
using (DisableMediaInsertionPrompt.Create())
{
if (!Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data))
// Using 'GetFileAttributesEx' to get file attributes of a pipe
// will inevitably open that pipe making it useless for a consumer,
// thus we need to handle this case separately
if (IsPipePath(path))
{
errorCode = GetFileAttributeInfoUsingFindFileApi(path, ref data);
}
else if (!Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data))
{
errorCode = Marshal.GetLastPInvokeError();
@ -79,19 +87,7 @@ namespace System.IO
// pagefile.sys case. As such we're probably stuck filtering out specific
// cases that we know we don't want to retry on.
Interop.Kernel32.WIN32_FIND_DATA findData = default;
using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(path!, ref findData))
{
if (handle.IsInvalid)
{
errorCode = Marshal.GetLastPInvokeError();
}
else
{
errorCode = Interop.Errors.ERROR_SUCCESS;
data.PopulateFrom(ref findData);
}
}
errorCode = GetFileAttributeInfoUsingFindFileApi(path!, ref data);
}
}
}
@ -104,12 +100,65 @@ namespace System.IO
case Interop.Errors.ERROR_PATH_NOT_FOUND:
case Interop.Errors.ERROR_NOT_READY: // Removable media not ready
// Return default value for backward compatibility
data.dwFileAttributes = -1;
data.dwFileAttributes = Interop.Kernel32.FileAttributes.INVALID_FILE_ATTRIBUTES;
return Interop.Errors.ERROR_SUCCESS;
}
}
return errorCode;
static int GetFileAttributeInfoUsingFindFileApi(string path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data)
{
Interop.Kernel32.WIN32_FIND_DATA findData = default;
using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(path, ref findData))
{
if (handle.IsInvalid)
{
return Marshal.GetLastPInvokeError();
}
else
{
data.PopulateFrom(ref findData);
return Interop.Errors.ERROR_SUCCESS;
}
}
}
}
/// <summary>
/// Tells whether a given path is a Windows pipe path.
/// Examples of pipe paths are:
/// <list type="bullet">
/// <item>
/// <c>\\.\pipe\pipeName</c> - local pipe path
/// </item>
/// <item>
/// <c>\\serverName\pipe\pipeName</c> - remote pipe path
/// </item>
/// </list>
/// </summary>
private static bool IsPipePath([NotNullWhen(true)] string? path)
{
if (path is null)
{
return false;
}
ReadOnlySpan<char> pathSpan = path.AsSpan();
if (!pathSpan.StartsWith(@"\\"))
{
return false;
}
pathSpan = pathSpan.Slice(2);
Span<Range> segments = stackalloc Range[3];
int written = pathSpan.Split(segments, '\\');
// 3 segments of a pipe path:
// 1) '.' or 'serverName'
// 2) Constant 'pipe' segment
// 3) Pipe name
return written == 3 && pathSpan[segments[1]].SequenceEqual("pipe");
}
internal static bool IsPathUnreachableError(int errorCode) =>

View File

@ -75,7 +75,8 @@ namespace System.IO
// but Exists is supposed to return true or false.
return false;
}
return (_data.dwFileAttributes != -1) && ((this is DirectoryInfo) == ((_data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY));
return (_data.dwFileAttributes != Interop.Kernel32.FileAttributes.INVALID_FILE_ATTRIBUTES) &&
((this is DirectoryInfo) == ((_data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY));
}
}

View File

@ -34,7 +34,7 @@ namespace System.IO
{
Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
int errorCode = FileSystem.FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: true);
bool result = (errorCode == Interop.Errors.ERROR_SUCCESS) && (data.dwFileAttributes != -1);
bool result = (errorCode == Interop.Errors.ERROR_SUCCESS) && (data.dwFileAttributes != Interop.Kernel32.FileAttributes.INVALID_FILE_ATTRIBUTES);
isDirectory = result && (data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) != 0;
return result;

View File

@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO.Pipes;
using System.Threading;
using Xunit;
namespace System.IO.Tests
@ -230,6 +232,44 @@ namespace System.IO.Tests
Assert.False(Exists(component));
}
[Fact]
[PlatformSpecific(TestPlatforms.Windows)] // Windows-specific pipes behavior
public void CheckPipeExistsViaFileApi()
{
var pipeName = GetNamedPipeServerStreamName();
using var server = new NamedPipeServerStream(pipeName);
Assert.True(Exists(@$"\\.\pipe\{pipeName}"));
}
[Fact]
[PlatformSpecific(TestPlatforms.Windows)] // Windows-specific pipes behavior
public void CheckPipeDoesNotExistViaFileApi()
{
var pipeName = GetNamedPipeServerStreamName();
new NamedPipeServerStream(pipeName).Dispose();
Assert.False(Exists(@$"\\.\pipe\{pipeName}"));
}
[Fact]
[PlatformSpecific(TestPlatforms.Windows)] // Windows-specific pipes behavior
public void CheckingPipeExistenceViaFileApiDoesNotInvalidateIt()
{
var pipeName = GetNamedPipeServerStreamName();
using var server = new NamedPipeServerStream(pipeName);
var connectedEvent = new ManualResetEventSlim(initialState: false);
var connectResult = server.BeginWaitForConnection(_ => { connectedEvent.Set(); }, state: null);
Assert.True(Exists(@$"\\.\pipe\{pipeName}"));
using var client = new NamedPipeClientStream(pipeName);
client.Connect(timeout: 1000);
server.EndWaitForConnection(connectResult);
Assert.True(connectedEvent.Wait(millisecondsTimeout: 1000));
}
#endregion
}