Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PipeOptions.FirstPipeInstance enum value #83936

Merged
1 change: 1 addition & 0 deletions src/libraries/System.IO.Pipes/ref/System.IO.Pipes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public enum PipeOptions
None = 0,
CurrentUserOnly = 536870912,
Asynchronous = 1073741824,
FirstPipeInstance = 524288
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}
public abstract partial class PipeStream : System.IO.Stream
{
Expand Down
54 changes: 27 additions & 27 deletions src/libraries/System.IO.Pipes/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema

Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple

There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Diagnostics;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -43,7 +42,7 @@ public sealed partial class NamedPipeServerStream : PipeStream
// in that the second process to come along and create a stream will find the pipe already in existence and will fail.
_instance = SharedServer.Get(
GetPipePath(".", pipeName),
(maxNumberOfServerInstances == MaxAllowedServerInstances) ? int.MaxValue : maxNumberOfServerInstances);
(maxNumberOfServerInstances == MaxAllowedServerInstances) ? int.MaxValue : maxNumberOfServerInstances, options);

_direction = direction;
_options = options;
Expand Down Expand Up @@ -249,14 +248,15 @@ private sealed class SharedServer
/// <summary>The concurrent number of concurrent streams using this instance.</summary>
private int _currentCount;

internal static SharedServer Get(string path, int maxCount)
internal static SharedServer Get(string path, int maxCount, PipeOptions pipeOptions)
{
Debug.Assert(!string.IsNullOrEmpty(path));
Debug.Assert(maxCount >= 1);

lock (s_servers)
{
SharedServer? server;
bool isFirstPipeInstance = (pipeOptions & PipeOptions.FirstPipeInstance) != 0;
if (s_servers.TryGetValue(path, out server))
{
// On Windows, if a subsequent server stream is created for the same pipe and with a different
Expand All @@ -268,15 +268,15 @@ internal static SharedServer Get(string path, int maxCount)
{
throw new IOException(SR.IO_AllPipeInstancesAreBusy);
}
else if (server._currentCount == maxCount)
else if (server._currentCount == maxCount || isFirstPipeInstance)
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
{
throw new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path));
}
}
else
{
// No instance exists yet for this path. Create one a new.
server = new SharedServer(path, maxCount);
server = new SharedServer(path, maxCount, isFirstPipeInstance);
s_servers.Add(path, server);
}

Expand Down Expand Up @@ -311,20 +311,30 @@ internal void Dispose(bool disposing)
}
}

private SharedServer(string path, int maxCount)
private SharedServer(string path, int maxCount, bool isFirstPipeInstance)
{
// Binding to an existing path fails, so we need to remove anything left over at this location.
// There's of course a race condition here, where it could be recreated by someone else between this
// deletion and the bind below, in which case we'll simply let the bind fail and throw.
Interop.Sys.Unlink(path); // ignore any failures
if (!isFirstPipeInstance)
{
// Binding to an existing path fails, so we need to remove anything left over at this location.
// There's of course a race condition here, where it could be recreated by someone else between this
// deletion and the bind below, in which case we'll simply let the bind fail and throw.
Interop.Sys.Unlink(path); // ignore any failures
}

bool isSocketBound = false;
// Start listening for connections on the path.
var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
try
{
socket.Bind(new UnixDomainSocketEndPoint(path));
isSocketBound = true;
socket.Listen(int.MaxValue);
}
catch (SocketException) when (isFirstPipeInstance && !isSocketBound)
{
socket.Dispose();
throw new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path));
}
catch
{
socket.Dispose();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
Expand Down Expand Up @@ -60,10 +59,9 @@ public NamedPipeServerStream(string pipeName, PipeDirection direction, int maxNu
/// Win32 note: this gets used for dwPipeMode. CreateNamedPipe allows you to specify PIPE_TYPE_BYTE/MESSAGE
/// and PIPE_READMODE_BYTE/MESSAGE independently, but this sets type and readmode to match.
/// </param>
/// <param name="options">PipeOption enum: None, Asynchronous, or Write-through
/// <param name="options">PipeOption enum: None, Asynchronous, Write-through, or FirstPipeInstance
/// Win32 note: this gets passed in with dwOpenMode to CreateNamedPipe. Asynchronous corresponds to
/// FILE_FLAG_OVERLAPPED option. PipeOptions enum doesn't expose FIRST_PIPE_INSTANCE option because
/// this sets that automatically based on the number of instances specified.
/// FILE_FLAG_OVERLAPPED option.
/// </param>
/// <param name="inBufferSize">Incoming buffer size, 0 or higher.
/// Note: this size is always advisory; OS uses a suggestion.
Expand Down Expand Up @@ -103,7 +101,7 @@ public NamedPipeServerStream(string pipeName, PipeDirection direction, int maxNu
{
throw new ArgumentOutOfRangeException(nameof(transmissionMode), SR.ArgumentOutOfRange_TransmissionModeByteOrMsg);
}
if ((options & ~(PipeOptions.WriteThrough | PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly)) != 0)
if ((options & ~(PipeOptions.WriteThrough | PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly | PipeOptions.FirstPipeInstance)) != 0)
{
throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_OptionsInvalid);
}
Expand Down Expand Up @@ -161,7 +159,7 @@ public Task WaitForConnectionAsync()
return WaitForConnectionAsync(CancellationToken.None);
}

public System.IAsyncResult BeginWaitForConnection(AsyncCallback? callback, object? state) =>
public IAsyncResult BeginWaitForConnection(AsyncCallback? callback, object? state) =>
TaskToAsyncResult.Begin(WaitForConnectionAsync(), callback, state);

public void EndWaitForConnection(IAsyncResult asyncResult) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.Runtime.Versioning;

namespace System.IO.Pipes
{
[Flags]
Expand All @@ -9,6 +11,7 @@ public enum PipeOptions
None = 0x0,
WriteThrough = unchecked((int)0x80000000),
Asynchronous = unchecked((int)0x40000000), // corresponds to FILE_FLAG_OVERLAPPED
CurrentUserOnly = unchecked((int)0x20000000)
CurrentUserOnly = unchecked((int)0x20000000),
FirstPipeInstance = unchecked((int)0x00080000)
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}
}
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public static void ReservedPipeName_Throws_ArgumentOutOfRangeException(PipeDirec
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1, PipeTransmissionMode.Byte));
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1, PipeTransmissionMode.Byte, PipeOptions.None));
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0));}
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0));
}

[Fact]
[SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")]
Expand Down Expand Up @@ -254,5 +255,16 @@ public static void Windows_ServerCloneWithDifferentDirection_Throws_Unauthorized
Assert.Throws<UnauthorizedAccessException>(() => new NamedPipeServerStream(uniqueServerName, PipeDirection.Out));
}
}

[Fact]
public static void PipeOptions_FirstPipeInstanceWithSameNameReuse_Throws_UnauthorizedAccessException()
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
{
string uniqueServerName = PipeStreamConformanceTests.GetUniquePipeName();
using (NamedPipeServerStream server = new NamedPipeServerStream(uniqueServerName, PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.FirstPipeInstance))
{
Assert.Throws<UnauthorizedAccessException>(() => new NamedPipeServerStream(uniqueServerName, PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.FirstPipeInstance));
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,22 @@ public async Task PingPong_Async()
}
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")]
public void NamedPipeOptionsFirstPipeInstance_Throws_WhenNameIsUsedAcrossProcesses()
{
var uniqueServerName = PipeStreamConformanceTests.GetUniquePipeName();
using (var firstServer = new NamedPipeServerStream(uniqueServerName, PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.FirstPipeInstance))
{
RemoteExecutor.Invoke(new Action<string>(CreateFirstPipeInstance_OtherProcess), uniqueServerName).Dispose();
}
}

private static void CreateFirstPipeInstance_OtherProcess(string uniqueServerName)
{
Assert.Throws<UnauthorizedAccessException>(() => new NamedPipeServerStream(uniqueServerName, PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.FirstPipeInstance));
}

private static void PingPong_OtherProcess(string inName, string outName)
{
// Create pipes with the supplied names
Expand Down