Skip to content

Commit 7d68b9b

Browse files
remove native named pipes impl
1 parent 0ba7868 commit 7d68b9b

File tree

4 files changed

+48
-188
lines changed

4 files changed

+48
-188
lines changed

module/PowerShellEditorServices/Start-EditorServices.ps1

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -257,32 +257,6 @@ function Test-NamedPipeName {
257257
return !(Test-Path $path)
258258
}
259259

260-
function Set-NamedPipeMode {
261-
param(
262-
[Parameter(Mandatory=$true)]
263-
[ValidateNotNullOrEmpty()]
264-
[string]
265-
$PipeFile
266-
)
267-
268-
if (($PSVersionTable.PSVersion.Major -le 5) -or $IsWindows) {
269-
return
270-
}
271-
272-
chmod $DEFAULT_USER_MODE $PipeFile
273-
274-
if ($IsLinux) {
275-
$mode = /usr/bin/stat -c "%a" $PipeFile
276-
}
277-
elseif ($IsMacOS) {
278-
$mode = /usr/bin/stat -f "%A" $PipeFile
279-
}
280-
281-
if ($mode -ne $DEFAULT_USER_MODE) {
282-
ExitWithError "Permissions to the pipe file were not set properly. Expected: $DEFAULT_USER_MODE Actual: $mode for file: $PipeFile"
283-
}
284-
}
285-
286260
LogSection "Console Encoding"
287261
Log $OutputEncoding
288262

@@ -316,9 +290,6 @@ function Set-PipeFileResult {
316290
)
317291

318292
$ResultTable[$PipeNameKey] = Get-NamedPipePath -PipeName $PipeNameValue
319-
if (($PSVersionTable.PSVersion.Major -ge 6) -and ($IsLinux -or $IsMacOS)) {
320-
Set-NamedPipeMode -PipeFile $ResultTable[$PipeNameKey]
321-
}
322293
}
323294

324295
# Add BundledModulesPath to $env:PSModulePath

src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeClientChannel.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public class NamedPipeClientChannel : ChannelBase
1616
private ILogger logger;
1717
private NamedPipeClientStream pipeClient;
1818

19+
// This int will be casted to a PipeOptions enum that only exists in .NET Core 2.1 and up which is why it's not available to us in .NET Standard.
20+
private const int CurrentUserOnly = 536870912;
21+
1922
private const string NAMED_PIPE_UNIX_PREFIX = "CoreFxPipe_";
2023

2124
public NamedPipeClientChannel(
@@ -56,18 +59,20 @@ public static async Task<NamedPipeClientChannel> ConnectAsync(
5659
{
5760
string pipeName = System.IO.Path.GetFileName(pipeFile);
5861

62+
var options = PipeOptions.Asynchronous;
5963
// on macOS and Linux, the named pipe name is prefixed by .NET Core
6064
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
6165
{
6266
pipeName = pipeFile.Split(new [] {NAMED_PIPE_UNIX_PREFIX}, StringSplitOptions.None)[1];
67+
options |= (PipeOptions)CurrentUserOnly;
6368
}
6469

6570
var pipeClient =
6671
new NamedPipeClientStream(
6772
".",
6873
pipeName,
6974
PipeDirection.InOut,
70-
PipeOptions.Asynchronous);
75+
options);
7176

7277
await pipeClient.ConnectAsync();
7378
var clientChannel = new NamedPipeClientChannel(pipeClient, logger);
@@ -77,4 +82,3 @@ public static async Task<NamedPipeClientChannel> ConnectAsync(
7782
}
7883
}
7984
}
80-

src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs

Lines changed: 37 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Collections.Generic;
1010
using System.IO;
1111
using System.IO.Pipes;
12+
using System.Reflection;
1213
using System.Runtime.InteropServices;
1314
using System.Security.AccessControl;
1415
using System.Security.Principal;
@@ -18,6 +19,15 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
1819
{
1920
public class NamedPipeServerListener : ServerListenerBase<NamedPipeServerChannel>
2021
{
22+
// This int will be casted to a PipeOptions enum that only exists in .NET Core 2.1 and up which is why it's not available to us in .NET Standard.
23+
private const int CurrentUserOnly = 536870912;
24+
25+
// In .NET Framework, NamedPipeServerStream has a constructor that takes in a PipeSecurity object. We will use reflection to call the constructor,
26+
// since .NET Framework doesn't have the `CurrentUserOnly` PipeOption.
27+
// doc: https://docs.microsoft.com/en-us/dotnet/api/system.io.pipes.namedpipeserverstream.-ctor?view=netframework-4.7.2#System_IO_Pipes_NamedPipeServerStream__ctor_System_String_System_IO_Pipes_PipeDirection_System_Int32_System_IO_Pipes_PipeTransmissionMode_System_IO_Pipes_PipeOptions_System_Int32_System_Int32_System_IO_Pipes_PipeSecurity_
28+
private static ConstructorInfo _netFrameworkPipeServerConstructor =
29+
typeof(NamedPipeServerStream).GetConstructor(new [] { typeof(string), typeof(PipeDirection), typeof(int), typeof(PipeTransmissionMode), typeof(PipeOptions), typeof(int), typeof(int), typeof(PipeSecurity) });
30+
2131
private ILogger logger;
2232
private string inOutPipeName;
2333
private readonly string outPipeName;
@@ -50,7 +60,8 @@ public override void Start()
5060
{
5161
try
5262
{
53-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
63+
// If we're running in Windows PowerShell, we use the constructor via Reflection
64+
if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework"))
5465
{
5566
PipeSecurity pipeSecurity = new PipeSecurity();
5667

@@ -72,35 +83,49 @@ public override void Start()
7283
PipeAccessRights.ReadWrite, AccessControlType.Allow));
7384
}
7485

75-
// Unfortunately, .NET Core does not support passing in a PipeSecurity object into the constructor for
76-
// NamedPipeServerStream so we are creating native Named Pipes and securing them using native APIs. The
77-
// issue on .NET Core regarding Named Pipe security is here: https://github.com/dotnet/corefx/issues/30170
78-
// 99% of this code was borrowed from PowerShell here:
79-
// https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs#L124-L256
80-
this.inOutPipeServer = NamedPipeNative.CreateNamedPipe(inOutPipeName, pipeSecurity);
86+
_netFrameworkPipeServerConstructor.Invoke(new object[]
87+
{
88+
inOutPipeName,
89+
PipeDirection.InOut,
90+
1, // maxNumberOfServerInstances
91+
PipeTransmissionMode.Byte,
92+
PipeOptions.Asynchronous,
93+
1024, // inBufferSize
94+
1024, // outBufferSize
95+
pipeSecurity
96+
});
97+
8198
if (this.outPipeName != null)
8299
{
83-
this.outPipeServer = NamedPipeNative.CreateNamedPipe(outPipeName, pipeSecurity);
100+
_netFrameworkPipeServerConstructor.Invoke(new object[]
101+
{
102+
outPipeName,
103+
PipeDirection.InOut,
104+
1, // maxNumberOfServerInstances
105+
PipeTransmissionMode.Byte,
106+
PipeOptions.Asynchronous,
107+
1024, // inBufferSize
108+
1024, // outBufferSize
109+
pipeSecurity
110+
});
84111
}
85112
}
86113
else
87114
{
88-
// This handles the Unix case since PipeSecurity is not supported on Unix.
89-
// Instead, we use chmod in Start-EditorServices.ps1
90115
this.inOutPipeServer = new NamedPipeServerStream(
91116
pipeName: inOutPipeName,
92117
direction: PipeDirection.InOut,
93118
maxNumberOfServerInstances: 1,
94119
transmissionMode: PipeTransmissionMode.Byte,
95-
options: PipeOptions.Asynchronous);
120+
options: PipeOptions.Asynchronous | (PipeOptions)CurrentUserOnly);
96121
if (this.outPipeName != null)
97122
{
98123
this.outPipeServer = new NamedPipeServerStream(
99124
pipeName: outPipeName,
100125
direction: PipeDirection.Out,
101126
maxNumberOfServerInstances: 1,
102127
transmissionMode: PipeTransmissionMode.Byte,
103-
options: PipeOptions.None);
128+
options: (PipeOptions)CurrentUserOnly);
104129
}
105130
}
106131
ListenForConnection();
@@ -171,143 +196,4 @@ private static async Task WaitForConnectionAsync(NamedPipeServerStream pipeServe
171196
await pipeServerStream.FlushAsync();
172197
}
173198
}
174-
175-
/// <summary>
176-
/// Native API for Named Pipes
177-
/// This code was borrowed from PowerShell here:
178-
/// https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs#L124-L256
179-
/// </summary>
180-
internal static class NamedPipeNative
181-
{
182-
#region Pipe constants
183-
184-
// Pipe open mode
185-
internal const uint PIPE_ACCESS_DUPLEX = 0x00000003;
186-
187-
// Pipe modes
188-
internal const uint PIPE_TYPE_BYTE = 0x00000000;
189-
internal const uint FILE_FLAG_OVERLAPPED = 0x40000000;
190-
internal const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;
191-
internal const uint PIPE_READMODE_BYTE = 0x00000000;
192-
193-
#endregion
194-
195-
#region Data structures
196-
197-
[StructLayout(LayoutKind.Sequential)]
198-
internal class SECURITY_ATTRIBUTES
199-
{
200-
/// <summary>
201-
/// The size, in bytes, of this structure. Set this value to the size of the SECURITY_ATTRIBUTES structure.
202-
/// </summary>
203-
public int NLength;
204-
205-
/// <summary>
206-
/// A pointer to a security descriptor for the object that controls the sharing of it.
207-
/// </summary>
208-
public IntPtr LPSecurityDescriptor = IntPtr.Zero;
209-
210-
/// <summary>
211-
/// A Boolean value that specifies whether the returned handle is inherited when a new process is created.
212-
/// </summary>
213-
public bool InheritHandle;
214-
215-
/// <summary>
216-
/// Initializes a new instance of the SECURITY_ATTRIBUTES class
217-
/// </summary>
218-
public SECURITY_ATTRIBUTES()
219-
{
220-
this.NLength = 12;
221-
}
222-
}
223-
224-
#endregion
225-
226-
#region Pipe methods
227-
228-
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
229-
internal static extern SafePipeHandle CreateNamedPipe(
230-
string lpName,
231-
uint dwOpenMode,
232-
uint dwPipeMode,
233-
uint nMaxInstances,
234-
uint nOutBufferSize,
235-
uint nInBufferSize,
236-
uint nDefaultTimeOut,
237-
SECURITY_ATTRIBUTES securityAttributes);
238-
239-
internal static SECURITY_ATTRIBUTES GetSecurityAttributes(GCHandle securityDescriptorPinnedHandle, bool inheritHandle = false)
240-
{
241-
SECURITY_ATTRIBUTES securityAttributes = new NamedPipeNative.SECURITY_ATTRIBUTES();
242-
securityAttributes.InheritHandle = inheritHandle;
243-
securityAttributes.NLength = (int)Marshal.SizeOf(securityAttributes);
244-
securityAttributes.LPSecurityDescriptor = securityDescriptorPinnedHandle.AddrOfPinnedObject();
245-
return securityAttributes;
246-
}
247-
248-
/// <summary>
249-
/// Helper method to create a PowerShell transport named pipe via native API, along
250-
/// with a returned .Net NamedPipeServerStream object wrapping the named pipe.
251-
/// </summary>
252-
/// <param name="pipeName">Named pipe core name.</param>
253-
/// <param name="securityDesc"></param>
254-
/// <returns>NamedPipeServerStream</returns>
255-
internal static NamedPipeServerStream CreateNamedPipe(
256-
string pipeName,
257-
PipeSecurity pipeSecurity)
258-
259-
{
260-
string fullPipeName = @"\\.\pipe\" + pipeName;
261-
CommonSecurityDescriptor securityDesc = new CommonSecurityDescriptor(false, false, pipeSecurity.GetSecurityDescriptorBinaryForm(), 0);
262-
263-
// Create optional security attributes based on provided PipeSecurity.
264-
NamedPipeNative.SECURITY_ATTRIBUTES securityAttributes = null;
265-
GCHandle? securityDescHandle = null;
266-
if (securityDesc != null)
267-
{
268-
byte[] securityDescBuffer = new byte[securityDesc.BinaryLength];
269-
securityDesc.GetBinaryForm(securityDescBuffer, 0);
270-
271-
securityDescHandle = GCHandle.Alloc(securityDescBuffer, GCHandleType.Pinned);
272-
securityAttributes = NamedPipeNative.GetSecurityAttributes(securityDescHandle.Value);
273-
}
274-
275-
// Create named pipe.
276-
SafePipeHandle pipeHandle = NamedPipeNative.CreateNamedPipe(
277-
fullPipeName,
278-
NamedPipeNative.PIPE_ACCESS_DUPLEX | NamedPipeNative.FILE_FLAG_FIRST_PIPE_INSTANCE | NamedPipeNative.FILE_FLAG_OVERLAPPED,
279-
NamedPipeNative.PIPE_TYPE_BYTE | NamedPipeNative.PIPE_READMODE_BYTE,
280-
1,
281-
1024,
282-
1024,
283-
0,
284-
securityAttributes);
285-
286-
int lastError = Marshal.GetLastWin32Error();
287-
if (securityDescHandle != null)
288-
{
289-
securityDescHandle.Value.Free();
290-
}
291-
292-
if (pipeHandle.IsInvalid)
293-
{
294-
throw new InvalidOperationException();
295-
}
296-
// Create the .Net NamedPipeServerStream wrapper.
297-
try
298-
{
299-
return new NamedPipeServerStream(
300-
PipeDirection.InOut,
301-
true, // IsAsync
302-
false, // IsConnected
303-
pipeHandle);
304-
}
305-
catch (Exception)
306-
{
307-
pipeHandle.Dispose();
308-
throw;
309-
}
310-
}
311-
#endregion
312-
}
313199
}

src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,12 @@ private async Task OnExecutionCompletedAsync(Task executeTask)
160160
// Respond to the disconnect request and stop the server
161161
await _disconnectRequestContext.SendResultAsync(null);
162162
Stop();
163+
return;
163164
}
164-
else
165-
{
166-
await _messageSender.SendEventAsync(
167-
TerminatedEvent.Type,
168-
new TerminatedEvent());
169-
}
165+
166+
await _messageSender.SendEventAsync(
167+
TerminatedEvent.Type,
168+
new TerminatedEvent());
170169
}
171170

172171
protected void Stop()

0 commit comments

Comments
 (0)