Skip to content

Commit

Permalink
Support named pipes for client/server communication.
Browse files Browse the repository at this point in the history
- Access the named pipe from --pipe flag, as per specification
- On Windows, pipes are implemented via. named pipes
- On Linux & MacOS pipes are implementated via. unix sockets

Co-authored-by: Roland Grunberg <[email protected]>
Co-authored-by: Jinbo Wang <[email protected]>
Signed-off-by: Roland Grunberg <[email protected]>
  • Loading branch information
rgrunber and testforstephen committed Oct 24, 2023
1 parent 1c837a4 commit 9c5cb1f
Showing 1 changed file with 186 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,23 @@
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.StandardProtocolFamily;
import java.net.UnixDomainSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.StandardOpenOption;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Stream;

import org.eclipse.core.runtime.Platform;

Expand Down Expand Up @@ -67,6 +80,51 @@ public OutputStream getOutputStream() throws IOException {
}
}

protected final class PipeStreamProvider implements StreamProvider {

private InputStream input;
private OutputStream output;

public PipeStreamProvider() {
initializeNamedPipe();
}

@Override
public InputStream getInputStream() throws IOException {
return input;
}

@Override
public OutputStream getOutputStream() throws IOException {
return output;
}

private void initializeNamedPipe() {
File pipeFile = getPipeFile();
if (pipeFile != null) {
if (isWindows()) {
try {
AsynchronousFileChannel channel = AsynchronousFileChannel.open(pipeFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE);
input = new NamedPipeInputStream(channel);
output = new NamedPipeOutputStream(channel);
} catch (IOException e) {
JavaLanguageServerPlugin.logException(e.getMessage(), e);
}
} else {
UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(pipeFile.toPath());
try {
SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX);
channel.connect(socketAddress);
input = new NamedPipeInputStream(channel);
output = new NamedPipeOutputStream(channel);
} catch (IOException e) {
JavaLanguageServerPlugin.logException(e.getMessage(), e);
}
}
}
}
}

protected final class StdIOStreamProvider implements StreamProvider {

/* (non-Javadoc)
Expand All @@ -87,6 +145,102 @@ public OutputStream getOutputStream() throws IOException {

}

public class NamedPipeInputStream extends InputStream {

private ReadableByteChannel unixChannel;
private AsynchronousFileChannel winChannel;
private ByteBuffer buffer = ByteBuffer.allocate(1024);
private int readyBytes = 0;

public NamedPipeInputStream(ReadableByteChannel channel) {
this.unixChannel = channel;
}

public NamedPipeInputStream(AsynchronousFileChannel channel) {
this.winChannel = channel;
}

@Override
public int read() throws IOException {
if (buffer.position() < readyBytes) {
return buffer.get() & 0xFF;
}
try {
buffer.clear();
if (winChannel != null) {
readyBytes = winChannel.read(buffer, 0).get();
} else {
readyBytes = unixChannel.read(buffer);
}
if (readyBytes == -1) {
return -1; // EOF
}
buffer.flip();
return buffer.get() & 0xFF;
} catch (InterruptedException | ExecutionException e) {
throw new IOException(e);
}
}
}

public class NamedPipeOutputStream extends OutputStream {

private WritableByteChannel unixChannel;
private AsynchronousFileChannel winChannel;
private ByteBuffer buffer = ByteBuffer.allocate(1);

public NamedPipeOutputStream(WritableByteChannel channel) {
this.unixChannel = channel;
}

public NamedPipeOutputStream(AsynchronousFileChannel channel) {
this.winChannel = channel;
}

@Override
public void write(int b) throws IOException {
buffer.clear();
buffer.put((byte) b);
buffer.position(0);
if (winChannel != null) {
Future<Integer> result = winChannel.write(buffer, 0);
try {
result.get();
} catch (Exception e) {
throw new IOException(e);
}
} else {
unixChannel.write(buffer);
}
}

@Override
public void write(byte[] b) throws IOException {
final int BUFFER_SIZE = 1024;
int blocks = b.length / BUFFER_SIZE;
int writeBytes = 0;
for (int i = 0; i <= blocks; i++) {
int offset = i * BUFFER_SIZE;
int length = Math.min(b.length - writeBytes, BUFFER_SIZE);
if (length <= 0) {
break;
}
writeBytes += length;
ByteBuffer buffer = ByteBuffer.wrap(b, offset, length);
if (winChannel != null) {
Future<Integer> result = winChannel.write(buffer, 0);
try {
result.get();
} catch (Exception e) {
throw new IOException(e);
}
} else {
unixChannel.write(buffer);
}
}
}
}

private StreamProvider provider;
private LanguageServerApplication application;

Expand All @@ -110,6 +264,10 @@ private StreamProvider createProvider() {
if (port != null) {
return new SocketStreamProvider(JDTEnvironmentUtils.getClientHost(), port);
}
File pipeFile = getPipeFile();
if (pipeFile != null) {
return new PipeStreamProvider();
}
return new StdIOStreamProvider();
}

Expand All @@ -125,4 +283,32 @@ protected static boolean isWindows() {
return Platform.OS_WIN32.equals(Platform.getOS());
}

/**
* Interactions (eg. exists()) with the pipe file (eg. named pipes on Windows)
* prior to establishing a connection may close it and cause failure
*
* @return a File representing the named pipe (Windows) / unix socket for
* communication with the client.
*/
private static File getPipeFile() {
Optional<String[]> procArgs = ProcessHandle.current().info().arguments();
String[] arguments = new String[0];
if (procArgs.isPresent()) {
arguments = procArgs.get();
} else {
// a new-line separated list of all command-line arguments passed in when launching Eclipse
String eclipseCommands = System.getProperty("eclipse.commands");
if (eclipseCommands != null) {
arguments = eclipseCommands.split("\n");
}
}
Optional<String> pipeArgs = Stream.of(arguments).filter(arg -> arg.contains("--pipe=")).findFirst();
if (pipeArgs.isPresent()) {
String pipeArg = pipeArgs.get();
String pipeFile = pipeArg.substring(pipeArg.indexOf('=') + 1);
return new File(pipeFile);
}
return null;
}

}

0 comments on commit 9c5cb1f

Please sign in to comment.