+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Runtime.InteropServices;
-using Microsoft.Win32.SafeHandles;
-
-internal static partial class Interop
-{
- internal static partial class Sys
- {
- internal static partial class Fcntl
- {
- [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FcntlSetCloseOnExec", SetLastError=true)]
- internal static extern int SetCloseOnExec(SafeHandle fd);
- }
- }
-}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ internal static partial class Fcntl
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FcntlSetFD", SetLastError=true)]
+ internal static extern int SetFD(SafeHandle fd, int flags);
+ }
+ }
+}
return result;
}
-int32_t SystemNative_FcntlSetCloseOnExec(intptr_t fd)
+int32_t SystemNative_FcntlSetFD(intptr_t fd, int32_t flags)
{
int result;
- while ((result = fcntl(ToFileDescriptor(fd), F_SETFD, FD_CLOEXEC)) < 0 && errno == EINTR);
+ while ((result = fcntl(ToFileDescriptor(fd), F_SETFD, ConvertOpenFlags(flags))) < 0 && errno == EINTR);
return result;
}
*
* Returns 0 for success; -1 for failure. Sets errno for failure.
*/
-DLLEXPORT int32_t SystemNative_FcntlSetCloseOnExec(intptr_t fd);
+DLLEXPORT int32_t SystemNative_FcntlSetFD(intptr_t fd, int32_t flags);
/**
* Determines if the current platform supports getting and setting pipe capacity.
<Compile Include="$(CommonPath)\CoreLib\Interop\Unix\Interop.Errors.cs">
<Link>Common\CoreLib\Interop\Unix\Interop.Errors.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Fcntl.SetCloseOnExec.cs">
- <Link>Common\Interop\Unix\Interop.Fcntl.SetCloseOnExec.cs</Link>
+ <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Fcntl.SetFD.cs">
+ <Link>Common\Interop\Unix\Interop.Fcntl.SetFD.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\CoreLib\Interop\Unix\Interop.IOErrors.cs">
<Link>Common\CoreLib\Interop\Unix\Interop.IOErrors.cs</Link>
if ((protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) != 0 && capacity > 0)
{
ownsFileStream = true;
- fileStream = CreateSharedBackingObject(protections, capacity);
-
- // If the MMF handle should not be inherited, mark the backing object fd as O_CLOEXEC.
- if (inheritability == HandleInheritability.None)
- {
- Interop.CheckIo(Interop.Sys.Fcntl.SetCloseOnExec(fileStream.SafeFileHandle));
- }
+ fileStream = CreateSharedBackingObject(protections, capacity, inheritability);
}
}
FileAccess.Read;
}
- private static FileStream CreateSharedBackingObject(Interop.Sys.MemoryMappedProtections protections, long capacity)
+ private static FileStream CreateSharedBackingObject(Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
{
- return CreateSharedBackingObjectUsingMemory(protections, capacity)
- ?? CreateSharedBackingObjectUsingFile(protections, capacity);
+ return CreateSharedBackingObjectUsingMemory(protections, capacity, inheritability)
+ ?? CreateSharedBackingObjectUsingFile(protections, capacity, inheritability);
}
// -----------------------------
// -----------------------------
private static FileStream CreateSharedBackingObjectUsingMemory(
- Interop.Sys.MemoryMappedProtections protections, long capacity)
+ Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
{
// The POSIX shared memory object name must begin with '/'. After that we just want something short and unique.
string mapName = "/corefx_map_" + Guid.NewGuid().ToString("N");
// causing it to preemptively throw from SetLength.
Interop.CheckIo(Interop.Sys.FTruncate(fd, capacity));
+ // shm_open sets CLOEXEC implicitly. If the inheritability requested is Inheritable, remove CLOEXEC.
+ if (inheritability == HandleInheritability.Inheritable &&
+ Interop.Sys.Fcntl.SetFD(fd, 0) == -1)
+ {
+ throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo());
+ }
+
// Wrap the file descriptor in a stream and return it.
return new FileStream(fd, TranslateProtectionsToFileAccess(protections));
}
}
}
- private static FileStream CreateSharedBackingObjectUsingFile(Interop.Sys.MemoryMappedProtections protections, long capacity)
+ private static FileStream CreateSharedBackingObjectUsingFile(Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
{
// We create a temporary backing file in TMPDIR. We don't bother putting it into subdirectories as the file exists
// extremely briefly: it's opened/created and then immediately unlinked.
string path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
- FileAccess access =
- (protections & (Interop.Sys.MemoryMappedProtections.PROT_READ | Interop.Sys.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.ReadWrite :
- (protections & (Interop.Sys.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.Write :
- FileAccess.Read;
+ FileShare share = inheritability == HandleInheritability.None ?
+ FileShare.ReadWrite :
+ FileShare.ReadWrite | FileShare.Inheritable;
// Create the backing file, then immediately unlink it so that it'll be cleaned up when no longer in use.
// Then enlarge it to the requested capacity.
const int DefaultBufferSize = 0x1000;
- var fs = new FileStream(path, FileMode.CreateNew, TranslateProtectionsToFileAccess(protections), FileShare.ReadWrite, DefaultBufferSize);
+ var fs = new FileStream(path, FileMode.CreateNew, TranslateProtectionsToFileAccess(protections), share, DefaultBufferSize);
try
{
Interop.CheckIo(Interop.Sys.Unlink(path));
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Fcntl.Pipe.cs">
<Link>Common\Interop\Unix\Interop.Fcntl.Pipe.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Fcntl.SetCloseOnExec.cs">
- <Link>Common\Interop\Unix\Interop.Fcntl.SetCloseOnExec.cs</Link>
+ <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Fcntl.SetFD.cs">
+ <Link>Common\Interop\Unix\Interop.Fcntl.SetFD.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\CoreLib\Interop\Unix\System.Native\Interop.FLock.cs">
<Link>Common\Interop\Unix\Interop.FLock.cs</Link>
SafePipeHandle serverHandle, clientHandle;
if (direction == PipeDirection.In)
- CreateAnonymousPipe(inheritability, reader: out serverHandle, writer: out clientHandle);
+ {
+ CreateAnonymousPipe(reader: out serverHandle, writer: out clientHandle);
+ }
else
- CreateAnonymousPipe(inheritability, reader: out clientHandle, writer: out serverHandle);
+ {
+ CreateAnonymousPipe(reader: out clientHandle, writer: out serverHandle);
+ }
+
+ // We always create pipes with both file descriptors being O_CLOEXEC.
+ // If inheritability is requested, we clear the O_CLOEXEC flag
+ // from the child descriptor so that it can be passed to a child process.
+ // We assume that the HandleInheritability only applies to the child fd,
+ // as if we allowed the server fd to be inherited, then when this process
+ // closes its end of the pipe, the client won't receive an EOF or broken
+ // pipe notification, as the child will still have open its dup of the fd.
+ if (inheritability == HandleInheritability.Inheritable &&
+ Interop.Sys.Fcntl.SetFD(clientHandle, 0) == -1)
+ {
+ throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo());
+ }
// Configure the pipe. For buffer size, the size applies to the pipe, rather than to
// just one end's file descriptor, so we only need to do this with one of the handles.
}
/// <summary>Creates an anonymous pipe.</summary>
- /// <param name="inheritability">The inheritability to try to use. This may not always be honored, depending on platform.</param>
/// <param name="reader">The resulting reader end of the pipe.</param>
/// <param name="writer">The resulting writer end of the pipe.</param>
- internal static unsafe void CreateAnonymousPipe(
- HandleInheritability inheritability, out SafePipeHandle reader, out SafePipeHandle writer)
+ internal static unsafe void CreateAnonymousPipe(out SafePipeHandle reader, out SafePipeHandle writer)
{
// Allocate the safe handle objects prior to calling pipe/pipe2, in order to help slightly in low-mem situations
reader = new SafePipeHandle();
writer = new SafePipeHandle();
- // Create the OS pipe
+ // Create the OS pipe. We always create it as O_CLOEXEC (trying to do so atomically) so that the
+ // file descriptors aren't inherited. Then if inheritability was requested, we opt-in the child file
+ // descriptor later; if the server file descriptor was also inherited, closing the server file
+ // descriptor would fail to signal EOF for the child side.
int* fds = stackalloc int[2];
- CreateAnonymousPipe(inheritability, fds);
+ Interop.CheckIo(Interop.Sys.Pipe(fds, Interop.Sys.PipeFlags.O_CLOEXEC));
// Store the file descriptors into our safe handles
reader.SetHandle(fds[Interop.Sys.ReadEndOfPipe]);
_outBufferSize;
}
- internal static unsafe void CreateAnonymousPipe(HandleInheritability inheritability, int* fdsptr)
- {
- var flags = (inheritability & HandleInheritability.Inheritable) == 0 ?
- Interop.Sys.PipeFlags.O_CLOEXEC : 0;
- Interop.CheckIo(Interop.Sys.Pipe(fdsptr, flags));
- }
-
internal static void ConfigureSocket(
Socket s, SafePipeHandle pipeHandle,
PipeDirection direction, int inBufferSize, int outBufferSize, HandleInheritability inheritability)
s.SendBufferSize = outBufferSize;
}
- if (inheritability != HandleInheritability.Inheritable)
+ // Sockets are created with O_CLOEXEC. If inheritability has been requested, we need to unset the flag.
+ if (inheritability == HandleInheritability.Inheritable &&
+ Interop.Sys.Fcntl.SetFD(s.SafeHandle, 0) == -1)
{
- Interop.Sys.Fcntl.SetCloseOnExec(pipeHandle); // ignore failures, best-effort attempt
+ throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo());
}
switch (direction)
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
+using System.Threading;
using Xunit;
namespace System.IO.Pipes.Tests
// Then spawn another process to communicate with.
using (var outbound = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable))
using (var inbound = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable))
- using (var remote = RemoteInvoke(new Func<string, string, int>(PingPong_OtherProcess), outbound.GetClientHandleAsString(), inbound.GetClientHandleAsString()))
+ using (var remote = RemoteInvoke(new Func<string, string, int>(ChildFunc), outbound.GetClientHandleAsString(), inbound.GetClientHandleAsString()))
{
// Close our local copies of the handles now that we've passed them of to the other process
outbound.DisposeLocalCopyOfClientHandle();
Assert.Equal(i, received);
}
}
+
+ int ChildFunc(string inHandle, string outHandle)
+ {
+ // Create the clients associated with the supplied handles
+ using (var inbound = new AnonymousPipeClientStream(PipeDirection.In, inHandle))
+ using (var outbound = new AnonymousPipeClientStream(PipeDirection.Out, outHandle))
+ {
+ // Repeatedly read then write a byte from and to the server
+ for (int i = 0; i < 10; i++)
+ {
+ int b = inbound.ReadByte();
+ outbound.WriteByte((byte)b);
+ }
+ }
+ return SuccessExitCode;
+ }
}
- private static int PingPong_OtherProcess(string inHandle, string outHandle)
+ [Fact]
+ public void ServerClosesPipe_ClientReceivesEof()
{
- // Create the clients associated with the supplied handles
- using (var inbound = new AnonymousPipeClientStream(PipeDirection.In, inHandle))
- using (var outbound = new AnonymousPipeClientStream(PipeDirection.Out, outHandle))
+ using (var pipe = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable))
+ using (var remote = RemoteInvoke(new Func<string, int>(ChildFunc), pipe.GetClientHandleAsString()))
{
- // Repeatedly read then write a byte from and to the server
- for (int i = 0; i < 10; i++)
+ pipe.DisposeLocalCopyOfClientHandle();
+ pipe.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5);
+
+ pipe.Dispose();
+
+ Assert.True(remote.Process.WaitForExit(30_000));
+ }
+
+ int ChildFunc(string clientHandle)
+ {
+ using (var pipe = new AnonymousPipeClientStream(PipeDirection.In, clientHandle))
{
- int b = inbound.ReadByte();
- outbound.WriteByte((byte)b);
+ for (int i = 1; i <= 5; i++)
+ {
+ Assert.Equal(i, pipe.ReadByte());
+ }
+ Assert.Equal(-1, pipe.ReadByte());
}
+ return SuccessExitCode;
}
- return SuccessExitCode;
}
+ [Fact]
+ public void ClientClosesPipe_ServerReceivesEof()
+ {
+ using (var pipe = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable))
+ using (var remote = RemoteInvoke(new Func<string, int>(ChildFunc), pipe.GetClientHandleAsString(), new RemoteInvokeOptions { CheckExitCode = false }))
+ {
+ pipe.DisposeLocalCopyOfClientHandle();
+
+ for (int i = 1; i <= 5; i++)
+ {
+ Assert.Equal(i, pipe.ReadByte());
+ }
+ Assert.Equal(-1, pipe.ReadByte());
+
+ remote.Process.Kill();
+ }
+
+ int ChildFunc(string clientHandle)
+ {
+ using (var pipe = new AnonymousPipeClientStream(PipeDirection.Out, clientHandle))
+ {
+ pipe.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5);
+ }
+ Thread.CurrentThread.Join();
+ return SuccessExitCode;
+ }
+ }
}
}
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
+using System.Globalization;
+using System.Security.Principal;
using System.Threading.Tasks;
+using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Pipes.Tests
[ActiveIssue(22271, TargetFrameworkMonikers.UapNotUapAot)]
public sealed class NamedPipeTest_CrossProcess : RemoteExecutorTestBase
{
+ [Fact]
+ public void InheritHandles_AvailableInChildProcess()
+ {
+ string pipeName = GetUniquePipeName();
+
+ using (var server = new NamedPipeServerStream(pipeName, PipeDirection.In))
+ using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.None, TokenImpersonationLevel.None, HandleInheritability.Inheritable))
+ {
+ Task.WaitAll(server.WaitForConnectionAsync(), client.ConnectAsync());
+ using (RemoteInvoke(new Func<string, int>(ChildFunc), client.SafePipeHandle.DangerousGetHandle().ToString()))
+ {
+ client.Dispose();
+ for (int i = 0; i < 5; i++)
+ {
+ Assert.Equal(i, server.ReadByte());
+ }
+ }
+ }
+
+ int ChildFunc(string handle)
+ {
+ using (var childClient = new NamedPipeClientStream(PipeDirection.Out, isAsync: false, isConnected: true, new SafePipeHandle((IntPtr)long.Parse(handle, CultureInfo.InvariantCulture), ownsHandle: true)))
+ {
+ for (int i = 0; i < 5; i++)
+ {
+ childClient.WriteByte((byte)i);
+ }
+ }
+ return SuccessExitCode;
+ }
+ }
+
[Fact]
public void PingPong_Sync()
{