Skip to content

Commit

Permalink
Add PipeOptions.FirstPipeInstance enum value (#83936)
Browse files Browse the repository at this point in the history
Co-authored-by: Gudipati.Tarun <[email protected]>
Co-authored-by: Adam Sitnik <[email protected]>
  • Loading branch information
3 people authored May 30, 2023
1 parent 305f2ba commit d4b31a8
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 45 deletions.
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
}
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 @@ private void Create(string pipeName, PipeDirection direction, int maxNumberOfSer
// 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)
{
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 @@ private void ValidateParameters(
{
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)
}
}
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()
{
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

0 comments on commit d4b31a8

Please sign in to comment.