Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ internal enum HandleFlags : uint
HANDLE_FLAG_PROTECT_FROM_CLOSE = 2
}

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetHandleInformation(SafeHandle hObject, out HandleFlags lpdwFlags);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetHandleInformation(SafeHandle hObject, HandleFlags dwMask, HandleFlags dwFlags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,17 +455,13 @@ private unsafe bool StartWithCreateProcess(ProcessStartInfo startInfo, SafeFileH
{
Debug.Assert(stdinHandle is not null && stdoutHandle is not null && stderrHandle is not null, "All or none of the standard handles must be provided.");

// The user can't specify invalid handle via ProcessStartInfo.Standar*Handle APIs.
// However, Console.OpenStandard*Handle() can return INVALID_HANDLE_VALUE for a process
// that was started with INVALID_HANDLE_VALUE as given standard handle.
// As soon as SafeFileHandle.IsInheritabe() is added, we can use it here to avoid unnecessary duplication.
inheritableStdinHandle = !stdinHandle.IsInvalid ? DuplicateAsInheritable(stdinHandle) : stdinHandle;
inheritableStdoutHandle = !stdoutHandle.IsInvalid ? DuplicateAsInheritable(stdoutHandle) : stdoutHandle;
inheritableStderrHandle = !stderrHandle.IsInvalid ? DuplicateAsInheritable(stderrHandle) : stderrHandle;

startupInfo.hStdInput = inheritableStdinHandle.DangerousGetHandle();
startupInfo.hStdOutput = inheritableStdoutHandle.DangerousGetHandle();
startupInfo.hStdError = inheritableStderrHandle.DangerousGetHandle();
DuplicateAsInheritableIfNeeded(stdinHandle, out inheritableStdinHandle);
DuplicateAsInheritableIfNeeded(stdoutHandle, out inheritableStdoutHandle);
DuplicateAsInheritableIfNeeded(stderrHandle, out inheritableStderrHandle);

startupInfo.hStdInput = (inheritableStdinHandle ?? stdinHandle).DangerousGetHandle();
startupInfo.hStdOutput = (inheritableStdoutHandle ?? stdoutHandle).DangerousGetHandle();
startupInfo.hStdError = (inheritableStderrHandle ?? stderrHandle).DangerousGetHandle();

// If STARTF_USESTDHANDLES is not set, the new process will inherit the standard handles.
startupInfo.dwFlags = Interop.Advapi32.StartupInfoOptions.STARTF_USESTDHANDLES;
Expand Down Expand Up @@ -597,6 +593,8 @@ ref processInfo // pointer to PROCESS_INFORMATION
}
finally
{
// Only dispose duplicated handles, not the original handles passed by the caller.
// When the handle was invalid or already inheritable, no duplication was needed.
inheritableStdinHandle?.Dispose();
inheritableStdoutHandle?.Dispose();
inheritableStderrHandle?.Dispose();
Expand All @@ -617,21 +615,29 @@ ref processInfo // pointer to PROCESS_INFORMATION
return true;
}

/// <summary>Duplicates a handle as inheritable so the child process can use it.</summary>
private static SafeFileHandle DuplicateAsInheritable(SafeFileHandle sourceHandle)
/// <summary>Duplicates a handle as inheritable if it's valid and not inheritable.</summary>
private static void DuplicateAsInheritableIfNeeded(SafeFileHandle sourceHandle, out SafeFileHandle? duplicatedHandle)
{
// The user can't specify invalid handle via ProcessStartInfo.Standar*Handle APIs.
// However, Console.OpenStandard*Handle() can return INVALID_HANDLE_VALUE for a process
// that was started with INVALID_HANDLE_VALUE as given standard handle.
if (sourceHandle.IsInvalid || sourceHandle.IsInheritable())
{
duplicatedHandle = null;
return;
}

IntPtr currentProcHandle = Interop.Kernel32.GetCurrentProcess();
if (!Interop.Kernel32.DuplicateHandle(currentProcHandle,
sourceHandle,
currentProcHandle,
out SafeFileHandle duplicatedHandle,
out duplicatedHandle,
0,
bInheritHandle: true,
Interop.Kernel32.HandleOptions.DUPLICATE_SAME_ACCESS))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return duplicatedHandle;
}

private static ConsoleEncoding GetEncoding(int codePage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using Xunit;

namespace System.IO.MemoryMappedFiles.Tests
Expand All @@ -20,12 +21,6 @@ public abstract partial class MemoryMappedFilesTestBase : FileCleanupTestBase
return pageSize;
});

[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool GetHandleInformation(IntPtr hObject, out uint lpdwFlags);

private const uint HANDLE_FLAG_INHERIT = 0x00000001;

[LibraryImport("kernel32.dll")]
private static partial void GetSystemInfo(out SYSTEM_INFO input);

Expand Down Expand Up @@ -53,9 +48,8 @@ protected static void AssertInheritability(SafeHandle handle, HandleInheritabili
{
if (OperatingSystem.IsWindows())
{
uint flags;
Assert.True(GetHandleInformation(handle.DangerousGetHandle(), out flags));
Assert.Equal(inheritability == HandleInheritability.Inheritable, (flags & HANDLE_FLAG_INHERIT) != 0);
using SafeFileHandle fileHandle = new(handle.DangerousGetHandle(), ownsHandle: false);
Assert.Equal(inheritability == HandleInheritability.Inheritable, fileHandle.IsInheritable());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -590,5 +590,19 @@ internal long GetFileLength()
FileStreamHelpers.CheckFileCall(result, Path);
return status.Size;
}

internal bool IsInheritableCore()
{
int flags = Interop.Sys.Fcntl.GetFD(this);
if (flags == -1)
{
throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), Path);
}

// FD_CLOEXEC means close-on-exec, i.e., NOT inheritable.
// If the flag is absent, the handle IS inheritable.
const int FD_CLOEXEC = 1;
return (flags & FD_CLOEXEC) == 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -461,5 +461,15 @@ unsafe long GetFileLengthCore()
return storageReadCapacity.DiskLength;
}
}

internal bool IsInheritableCore()
{
if (!Interop.Kernel32.GetHandleInformation(this, out Interop.Kernel32.HandleFlags flags))
{
throw Win32Marshal.GetExceptionForLastWin32Error(Path);
}

return (flags & Interop.Kernel32.HandleFlags.HANDLE_FLAG_INHERIT) != 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,28 @@ public System.IO.FileHandleType Type
return (System.IO.FileHandleType)cachedType;
}
}

/// <summary>
/// Gets a value indicating whether the handle is inheritable by child processes.
/// </summary>
/// <returns>
/// <see langword="true"/> if the handle is inheritable by child processes; otherwise, <see langword="false"/>.
/// </returns>
/// <exception cref="ObjectDisposedException">The handle is closed.</exception>
/// <remarks>
/// <para>
/// On Windows, this method calls the <c>GetHandleInformation</c> system call and checks for the <c>HANDLE_FLAG_INHERIT</c> flag.
/// </para>
/// <para>
/// On Unix, this method calls <c>fcntl</c> with <c>F_GETFD</c> and checks for the absence of the <c>FD_CLOEXEC</c> flag.
/// A file descriptor without the <c>FD_CLOEXEC</c> flag set is inheritable by child processes.
/// </para>
/// </remarks>
public bool IsInheritable()
{
ObjectDisposedException.ThrowIf(IsClosed, this);

return IsInheritableCore();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,9 @@
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.PipeOptions.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.PipeOptions.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.HandleInformation.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.HandleInformation.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreatePipe_SafeFileHandle.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.CreatePipe_SafeFileHandle.cs</Link>
</Compile>
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public SafeFileHandle() : base (default(bool)) { }
public SafeFileHandle(System.IntPtr preexistingHandle, bool ownsHandle) : base (default(bool)) { }
public override bool IsInvalid { get { throw null; } }
public bool IsAsync { get { throw null; } }
public bool IsInheritable() { throw null; }
public System.IO.FileHandleType Type { get { throw null; } }
protected override bool ReleaseHandle() { throw null; }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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 Microsoft.Win32.SafeHandles;
using Xunit;

Expand Down Expand Up @@ -160,5 +161,55 @@ public void PreallocationSizeVeryLargeThrowsCorrectHResult()
Assert.True(ex.HResult is ERROR_INVALID_PARAMETER or ERROR_DISK_FULL);
}
}

[Theory]
[InlineData(FileShare.None)]
[InlineData(FileShare.Inheritable)]
public void SafeFileHandle_IsInheritable_ReturnsCorrectInformation(FileShare share)
{
using SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, share);
Assert.Equal((share & FileShare.Inheritable) != 0, handle.IsInheritable());
}

[Theory]
[InlineData(FileShare.None)]
[InlineData(FileShare.Inheritable)]
public void SafeFileHandle_IsInheritable_ReturnsCorrectInformation_FromIntPtr(FileShare share)
{
using SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, share);
using SafeFileHandle createdFromIntPtr = new(handle.DangerousGetHandle(), ownsHandle: false);
Assert.Equal((share & FileShare.Inheritable) != 0, createdFromIntPtr.IsInheritable());
}

[Fact]
[SkipOnPlatform(TestPlatforms.Browser, "Pipes are not supported on browser")]
public void SafeFileHandle_IsInheritable_InheritablePipe()
{
using AnonymousPipeServerStream pipeServer = new(PipeDirection.Out, HandleInheritability.Inheritable);
using SafeFileHandle handle = new(pipeServer.ClientSafePipeHandle.DangerousGetHandle(), ownsHandle: false);
Assert.True(handle.IsInheritable());
pipeServer.DisposeLocalCopyOfClientHandle();
}

[Fact]
[SkipOnPlatform(TestPlatforms.Browser, "Pipes are not supported on browser")]
public void SafeFileHandle_IsInheritable_NonInheritablePipe()
{
SafeFileHandle.CreateAnonymousPipe(out SafeFileHandle readHandle, out SafeFileHandle writeHandle);
using (readHandle)
using (writeHandle)
{
Assert.False(readHandle.IsInheritable());
Assert.False(writeHandle.IsInheritable());
}
}

[Fact]
public void SafeFileHandle_IsInheritable_ThrowsOnDisposedHandle()
{
SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write);
handle.Dispose();
Assert.Throws<ObjectDisposedException>(() => handle.IsInheritable());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
<Compile Include="$(CommonTestPath)System\IO\VirtualDriveHelper.Windows.cs" Link="Common\System\IO\VirtualDriveHelper.Windows.cs" />
<ProjectReference Include="$(LibrariesProjectRoot)System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.IO.FileSystem.AccessControl\src\System.IO.FileSystem.AccessControl.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.IO.Pipes\src\System.IO.Pipes.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'browser'">
<Compile Include="FileSystemTest.Browser.cs" />
Expand Down
3 changes: 0 additions & 3 deletions src/libraries/System.Threading.Overlapped/tests/DllImport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ internal static extern Win32Handle CreateFile(string lpFileName,
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CloseHandle(IntPtr handle);

[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool GetHandleInformation(IntPtr handle, out int flags);

internal const int ERROR_IO_PENDING = 0x000003E5;

[Flags]
Expand Down
Loading