Skip to content

Commit d4b31a8

Browse files
Tarun047Gudipati.Tarunadamsitnik
authored
Add PipeOptions.FirstPipeInstance enum value (#83936)
Co-authored-by: Gudipati.Tarun <[email protected]> Co-authored-by: Adam Sitnik <[email protected]>
1 parent 305f2ba commit d4b31a8

File tree

7 files changed

+85
-45
lines changed

7 files changed

+85
-45
lines changed

src/libraries/System.IO.Pipes/ref/System.IO.Pipes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public enum PipeOptions
9595
None = 0,
9696
CurrentUserOnly = 536870912,
9797
Asynchronous = 1073741824,
98+
FirstPipeInstance = 524288
9899
}
99100
public abstract partial class PipeStream : System.IO.Stream
100101
{

src/libraries/System.IO.Pipes/src/Resources/Strings.resx

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<root>
3-
<!--
4-
Microsoft ResX Schema
5-
3+
<!--
4+
Microsoft ResX Schema
5+
66
Version 2.0
7-
8-
The primary goals of this format is to allow a simple XML format
9-
that is mostly human readable. The generation and parsing of the
10-
various data types are done through the TypeConverter classes
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
1111
associated with the data types.
12-
12+
1313
Example:
14-
14+
1515
... ado.net/XML headers & schema ...
1616
<resheader name="resmimetype">text/microsoft-resx</resheader>
1717
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
2626
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
2727
<comment>This is a comment</comment>
2828
</data>
29-
30-
There are any number of "resheader" rows that contain simple
29+
30+
There are any number of "resheader" rows that contain simple
3131
name/value pairs.
32-
33-
Each data row contains a name, and value. The row also contains a
34-
type or mimetype. Type corresponds to a .NET class that support
35-
text/value conversion through the TypeConverter architecture.
36-
Classes that don't support this are serialized and stored with the
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
3737
mimetype set.
38-
39-
The mimetype is used for serialized objects, and tells the
40-
ResXResourceReader how to depersist the object. This is currently not
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
4141
extensible. For a given mimetype the value must be set accordingly:
42-
43-
Note - application/x-microsoft.net.object.binary.base64 is the format
44-
that the ResXResourceWriter will generate, however the reader can
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
4545
read any of the formats listed below.
46-
46+
4747
mimetype: application/x-microsoft.net.object.binary.base64
48-
value : The object must be serialized with
48+
value : The object must be serialized with
4949
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
5050
: and then encoded with base64 encoding.
51-
51+
5252
mimetype: application/x-microsoft.net.object.soap.base64
53-
value : The object must be serialized with
53+
value : The object must be serialized with
5454
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
5555
: and then encoded with base64 encoding.
5656
5757
mimetype: application/x-microsoft.net.object.bytearray.base64
58-
value : The object must be serialized into a byte array
58+
value : The object must be serialized into a byte array
5959
: using a System.ComponentModel.TypeConverter
6060
: and then encoded with base64 encoding.
6161
-->

src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Diagnostics;
77
using System.Net.Sockets;
88
using System.Runtime.InteropServices;
9-
using System.Security;
109
using System.Threading;
1110
using System.Threading.Tasks;
1211

@@ -43,7 +42,7 @@ private void Create(string pipeName, PipeDirection direction, int maxNumberOfSer
4342
// in that the second process to come along and create a stream will find the pipe already in existence and will fail.
4443
_instance = SharedServer.Get(
4544
GetPipePath(".", pipeName),
46-
(maxNumberOfServerInstances == MaxAllowedServerInstances) ? int.MaxValue : maxNumberOfServerInstances);
45+
(maxNumberOfServerInstances == MaxAllowedServerInstances) ? int.MaxValue : maxNumberOfServerInstances, options);
4746

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

252-
internal static SharedServer Get(string path, int maxCount)
251+
internal static SharedServer Get(string path, int maxCount, PipeOptions pipeOptions)
253252
{
254253
Debug.Assert(!string.IsNullOrEmpty(path));
255254
Debug.Assert(maxCount >= 1);
256255

257256
lock (s_servers)
258257
{
259258
SharedServer? server;
259+
bool isFirstPipeInstance = (pipeOptions & PipeOptions.FirstPipeInstance) != 0;
260260
if (s_servers.TryGetValue(path, out server))
261261
{
262262
// On Windows, if a subsequent server stream is created for the same pipe and with a different
@@ -268,15 +268,15 @@ internal static SharedServer Get(string path, int maxCount)
268268
{
269269
throw new IOException(SR.IO_AllPipeInstancesAreBusy);
270270
}
271-
else if (server._currentCount == maxCount)
271+
else if (server._currentCount == maxCount || isFirstPipeInstance)
272272
{
273273
throw new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path));
274274
}
275275
}
276276
else
277277
{
278278
// No instance exists yet for this path. Create one a new.
279-
server = new SharedServer(path, maxCount);
279+
server = new SharedServer(path, maxCount, isFirstPipeInstance);
280280
s_servers.Add(path, server);
281281
}
282282

@@ -311,20 +311,30 @@ internal void Dispose(bool disposing)
311311
}
312312
}
313313

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

324+
bool isSocketBound = false;
321325
// Start listening for connections on the path.
322326
var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
323327
try
324328
{
325329
socket.Bind(new UnixDomainSocketEndPoint(path));
330+
isSocketBound = true;
326331
socket.Listen(int.MaxValue);
327332
}
333+
catch (SocketException) when (isFirstPipeInstance && !isSocketBound)
334+
{
335+
socket.Dispose();
336+
throw new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path));
337+
}
328338
catch
329339
{
330340
socket.Dispose();

src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

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

164-
public System.IAsyncResult BeginWaitForConnection(AsyncCallback? callback, object? state) =>
162+
public IAsyncResult BeginWaitForConnection(AsyncCallback? callback, object? state) =>
165163
TaskToAsyncResult.Begin(WaitForConnectionAsync(), callback, state);
166164

167165
public void EndWaitForConnection(IAsyncResult asyncResult) =>
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Runtime.Versioning;
5+
46
namespace System.IO.Pipes
57
{
68
[Flags]
@@ -9,6 +11,7 @@ public enum PipeOptions
911
None = 0x0,
1012
WriteThrough = unchecked((int)0x80000000),
1113
Asynchronous = unchecked((int)0x40000000), // corresponds to FILE_FLAG_OVERLAPPED
12-
CurrentUserOnly = unchecked((int)0x20000000)
14+
CurrentUserOnly = unchecked((int)0x20000000),
15+
FirstPipeInstance = unchecked((int)0x00080000)
1316
}
1417
}

src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateServer.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ public static void ReservedPipeName_Throws_ArgumentOutOfRangeException(PipeDirec
5252
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1));
5353
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1, PipeTransmissionMode.Byte));
5454
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1, PipeTransmissionMode.Byte, PipeOptions.None));
55-
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0));}
55+
AssertExtensions.Throws<ArgumentOutOfRangeException>("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0));
56+
}
5657

5758
[Fact]
5859
[SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")]
@@ -254,5 +255,16 @@ public static void Windows_ServerCloneWithDifferentDirection_Throws_Unauthorized
254255
Assert.Throws<UnauthorizedAccessException>(() => new NamedPipeServerStream(uniqueServerName, PipeDirection.Out));
255256
}
256257
}
258+
259+
[Fact]
260+
public static void PipeOptions_FirstPipeInstanceWithSameNameReuse_Throws_UnauthorizedAccessException()
261+
{
262+
string uniqueServerName = PipeStreamConformanceTests.GetUniquePipeName();
263+
using (NamedPipeServerStream server = new NamedPipeServerStream(uniqueServerName, PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.FirstPipeInstance))
264+
{
265+
Assert.Throws<UnauthorizedAccessException>(() => new NamedPipeServerStream(uniqueServerName, PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.FirstPipeInstance));
266+
}
267+
}
268+
257269
}
258270
}

src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CrossProcess.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,22 @@ public async Task PingPong_Async()
103103
}
104104
}
105105

106+
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
107+
[SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")]
108+
public void NamedPipeOptionsFirstPipeInstance_Throws_WhenNameIsUsedAcrossProcesses()
109+
{
110+
var uniqueServerName = PipeStreamConformanceTests.GetUniquePipeName();
111+
using (var firstServer = new NamedPipeServerStream(uniqueServerName, PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.FirstPipeInstance))
112+
{
113+
RemoteExecutor.Invoke(new Action<string>(CreateFirstPipeInstance_OtherProcess), uniqueServerName).Dispose();
114+
}
115+
}
116+
117+
private static void CreateFirstPipeInstance_OtherProcess(string uniqueServerName)
118+
{
119+
Assert.Throws<UnauthorizedAccessException>(() => new NamedPipeServerStream(uniqueServerName, PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.FirstPipeInstance));
120+
}
121+
106122
private static void PingPong_OtherProcess(string inName, string outName)
107123
{
108124
// Create pipes with the supplied names

0 commit comments

Comments
 (0)