mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-22 06:02:11 +02:00
Bug 562776: Use Windows ConPTY API instead of WinPTY
There are lots of bugs in WinPTY, while upgrading WinPTY would resolve some of them, there are others that are unresolvable. See https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/ for a backgrounder on the general subject. In this first version ConPTY won't be enabled by default, it requires system property org.eclipse.cdt.core.conpty_enabled=true to be set. i.e. start Eclipse with: -vmargs -Dorg.eclipse.cdt.core.conpty_enabled=true In a future version the default will change to on if available, so to force it off use: org.eclipse.cdt.core.conpty_enabled=false Change-Id: Ib2df0095027c23f20daa6aa044d9e5f0b0443164
This commit is contained in:
parent
2443cfbeff
commit
4e92239952
13 changed files with 752 additions and 19 deletions
|
@ -10,7 +10,9 @@ Export-Package: org.eclipse.cdt.core;native=split;mandatory:=native,
|
||||||
org.eclipse.cdt.utils;native=split;mandatory:=native,
|
org.eclipse.cdt.utils;native=split;mandatory:=native,
|
||||||
org.eclipse.cdt.utils.pty;version="5.7",
|
org.eclipse.cdt.utils.pty;version="5.7",
|
||||||
org.eclipse.cdt.utils.spawner;version="5.7"
|
org.eclipse.cdt.utils.spawner;version="5.7"
|
||||||
Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.1.0,4.0.0)"
|
Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.1.0,4.0.0)",
|
||||||
|
com.sun.jna;bundle-version="[5.6.0,6.0.0)";resolution:=optional,
|
||||||
|
com.sun.jna.platform;bundle-version="[5.6.0,6.0.0)";resolution:=optional
|
||||||
Bundle-ActivationPolicy: lazy
|
Bundle-ActivationPolicy: lazy
|
||||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||||
Automatic-Module-Name: org.eclipse.cdt.core.native
|
Automatic-Module-Name: org.eclipse.cdt.core.native
|
||||||
|
|
|
@ -16,6 +16,8 @@ package org.eclipse.cdt.internal.core.natives;
|
||||||
import org.eclipse.osgi.util.NLS;
|
import org.eclipse.osgi.util.NLS;
|
||||||
|
|
||||||
public class Messages extends NLS {
|
public class Messages extends NLS {
|
||||||
|
public static String PTY_FailedToStartConPTY;
|
||||||
|
public static String PTY_NoClassDefFoundError;
|
||||||
public static String Util_exception_cannotCreatePty;
|
public static String Util_exception_cannotCreatePty;
|
||||||
public static String Util_exception_cannotSetTerminalSize;
|
public static String Util_exception_cannotSetTerminalSize;
|
||||||
public static String Util_error_cannotRun;
|
public static String Util_error_cannotRun;
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
# Martin Oberhuber (Wind River) - [303083] Split from CCorePluginResources.properties
|
# Martin Oberhuber (Wind River) - [303083] Split from CCorePluginResources.properties
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
PTY_FailedToStartConPTY=Failed start a new style ConPTY. This is expected on Windows machines before Windows 10 1904 version and will fall back to WinPTY. Please consider upgrading to a newer version of Windows 10 to take advantage of the new improved console behavior.
|
||||||
|
PTY_NoClassDefFoundError=Failed start a new style ConPTY due to NoClassDefFoundError which probably means that the optional dependency on JNA is not available. Consider updating your product to include JNA to enable the ConPTY.
|
||||||
Util_exception_cannotCreatePty=Cannot create pty
|
Util_exception_cannotCreatePty=Cannot create pty
|
||||||
Util_exception_closeError=close error
|
Util_exception_closeError=close error
|
||||||
Util_exception_cannotSetTerminalSize=Setting terminal size is not supported
|
Util_exception_cannotSetTerminalSize=Setting terminal size is not supported
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2021 Kichwa Coders Canada Inc. and others.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.eclipse.cdt.utils;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of proper Windows quoting based on blog post:
|
||||||
|
* https://docs.microsoft.com/en-ca/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
|
||||||
|
*
|
||||||
|
* @noreference This class is not intended to be referenced by clients.
|
||||||
|
*/
|
||||||
|
public class WindowsArgumentQuoter {
|
||||||
|
|
||||||
|
public static String quoteArgv(String[] cmdarray, boolean force) {
|
||||||
|
StringBuilder quoted = new StringBuilder();
|
||||||
|
for (String arg : cmdarray) {
|
||||||
|
quoteArg(arg, quoted, force);
|
||||||
|
quoted.append(' ');
|
||||||
|
}
|
||||||
|
quoted.deleteCharAt(quoted.length() - 1);
|
||||||
|
return quoted.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pattern spaces = Pattern.compile(".*\\s.*"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
private static void quoteArg(String arg, StringBuilder quoted, boolean force) {
|
||||||
|
|
||||||
|
// Unless we're told otherwise, don't quote unless we actually
|
||||||
|
// need to do so --- hopefully avoid problems if programs won't
|
||||||
|
// parse quotes properly
|
||||||
|
|
||||||
|
if (!force && !arg.isEmpty() && !spaces.matcher(arg).matches()) {
|
||||||
|
quoted.append(arg);
|
||||||
|
} else {
|
||||||
|
quoted.append('"');
|
||||||
|
for (int i = 0; i < arg.length(); i++) {
|
||||||
|
int numberBackslashes = 0;
|
||||||
|
|
||||||
|
while (i < arg.length() && arg.charAt(i) == '\\') {
|
||||||
|
i++;
|
||||||
|
numberBackslashes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == arg.length()) {
|
||||||
|
// Escape all backslashes, but let the terminating
|
||||||
|
// double quotation mark we add below be interpreted
|
||||||
|
// as a metacharacter.
|
||||||
|
quoted.append("\\".repeat(numberBackslashes * 2)); //$NON-NLS-1$
|
||||||
|
break;
|
||||||
|
} else if (arg.charAt(i) == '"') {
|
||||||
|
|
||||||
|
// Escape all backslashes and the following
|
||||||
|
// double quotation mark.
|
||||||
|
quoted.append("\\".repeat(numberBackslashes)); //$NON-NLS-1$
|
||||||
|
quoted.append('"');
|
||||||
|
} else {
|
||||||
|
// Backslashes aren't special here.
|
||||||
|
quoted.append("\\".repeat(numberBackslashes)); //$NON-NLS-1$
|
||||||
|
quoted.append(arg.charAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quoted.append('"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,357 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2021 Kichwa Coders Canada Inc. and others.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.eclipse.cdt.utils.pty;
|
||||||
|
|
||||||
|
import static com.sun.jna.platform.win32.WinBase.CREATE_UNICODE_ENVIRONMENT;
|
||||||
|
import static com.sun.jna.platform.win32.WinBase.EXTENDED_STARTUPINFO_PRESENT;
|
||||||
|
import static com.sun.jna.platform.win32.WinBase.INFINITE;
|
||||||
|
import static com.sun.jna.platform.win32.WinBase.STARTF_USESTDHANDLES;
|
||||||
|
import static com.sun.jna.platform.win32.WinBase.WAIT_OBJECT_0;
|
||||||
|
import static com.sun.jna.platform.win32.WinError.S_OK;
|
||||||
|
import static com.sun.jna.platform.win32.WinNT.PROCESS_QUERY_INFORMATION;
|
||||||
|
import static com.sun.jna.platform.win32.WinNT.SYNCHRONIZE;
|
||||||
|
import static org.eclipse.cdt.utils.pty.ConPTYKernel32.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import org.eclipse.cdt.utils.WindowsArgumentQuoter;
|
||||||
|
|
||||||
|
import com.sun.jna.Memory;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.platform.win32.BaseTSD.DWORD_PTR;
|
||||||
|
import com.sun.jna.platform.win32.BaseTSD.SIZE_T;
|
||||||
|
import com.sun.jna.platform.win32.Kernel32;
|
||||||
|
import com.sun.jna.platform.win32.Kernel32Util;
|
||||||
|
import com.sun.jna.platform.win32.WinBase;
|
||||||
|
import com.sun.jna.platform.win32.WinBase.PROCESS_INFORMATION;
|
||||||
|
import com.sun.jna.platform.win32.WinDef;
|
||||||
|
import com.sun.jna.platform.win32.WinDef.DWORD;
|
||||||
|
import com.sun.jna.platform.win32.WinDef.PVOID;
|
||||||
|
import com.sun.jna.platform.win32.WinError;
|
||||||
|
import com.sun.jna.platform.win32.WinNT;
|
||||||
|
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||||
|
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
|
||||||
|
import com.sun.jna.platform.win32.WinNT.HRESULT;
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A JNA implementation for ConPTY to provide a Windows native (as opposed to WinPTY)
|
||||||
|
* implementation of a PTY.
|
||||||
|
*
|
||||||
|
* This class should be accessed/created via the PTY class which will use ConPTY when it
|
||||||
|
* is available.
|
||||||
|
*
|
||||||
|
* @noreference This class is not intended to be referenced by clients.
|
||||||
|
*/
|
||||||
|
public class ConPTY {
|
||||||
|
|
||||||
|
private Handles handles = new Handles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handles that need to be closed when the PTY is done
|
||||||
|
*/
|
||||||
|
private static class Handles {
|
||||||
|
private HANDLEByReference pseudoConsole;
|
||||||
|
private ConPTYKernel32.STARTUPINFOEX startupInfo;
|
||||||
|
private Memory threadAttributeListMemory;
|
||||||
|
private WinBase.PROCESS_INFORMATION processInformation;
|
||||||
|
private HANDLEByReference pipeOut;
|
||||||
|
private HANDLEByReference pipeIn;
|
||||||
|
|
||||||
|
/** Saved for convenience to make it easier to identify/find the process in process explorer */
|
||||||
|
public int pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Windows Pseudo Console (ConPTY) that an application can be attached to.
|
||||||
|
*/
|
||||||
|
public ConPTY() throws IOException {
|
||||||
|
handles.pseudoConsole = new HANDLEByReference();
|
||||||
|
handles.pipeIn = new HANDLEByReference();
|
||||||
|
handles.pipeOut = new HANDLEByReference();
|
||||||
|
|
||||||
|
var phPipePTYIn = new WinNT.HANDLEByReference();
|
||||||
|
var phPipePTYOut = new WinNT.HANDLEByReference();
|
||||||
|
|
||||||
|
boolean res;
|
||||||
|
res = ConPTYKernel32.INSTANCE.CreatePipe(phPipePTYIn, handles.pipeOut, null, 0);
|
||||||
|
checkErr(res, "CreatePipe"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
res = ConPTYKernel32.INSTANCE.CreatePipe(handles.pipeIn, phPipePTYOut, null, 0);
|
||||||
|
checkErr(res, "CreatePipe"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
// The console will be resized later with ResizePseudoConsole, start with the old classic size!
|
||||||
|
var consoleSize = new ConPTYKernel32.COORD_ByValue();
|
||||||
|
consoleSize.X = (short) 80;
|
||||||
|
consoleSize.Y = (short) 24;
|
||||||
|
|
||||||
|
var hr = ConPTYKernel32.INSTANCE.CreatePseudoConsole(consoleSize, phPipePTYIn.getValue(),
|
||||||
|
phPipePTYOut.getValue(), new WinDef.DWORD(0), handles.pseudoConsole);
|
||||||
|
checkErr(hr, "CreatePseudoConsole"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
res = ConPTYKernel32.INSTANCE.CloseHandle(phPipePTYOut.getValue());
|
||||||
|
checkErr(res, "CloseHandle"); //$NON-NLS-1$
|
||||||
|
res = ConPTYKernel32.INSTANCE.CloseHandle(phPipePTYIn.getValue());
|
||||||
|
checkErr(res, "CloseHandle"); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given process in the PTY
|
||||||
|
*
|
||||||
|
* @param cmdarray Command and arguments that will be quotes using standard Windows rules to make a
|
||||||
|
* command line. See {@link WindowsArgumentQuoter}
|
||||||
|
* @param envp
|
||||||
|
* @param dir
|
||||||
|
* @return the PID
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public int exec(String[] cmdarray, String[] envp, String dir) throws IOException {
|
||||||
|
String quoted = WindowsArgumentQuoter.quoteArgv(cmdarray, false);
|
||||||
|
handles.startupInfo = new ConPTYKernel32.STARTUPINFOEX();
|
||||||
|
handles.threadAttributeListMemory = PrepareStartupInformation(handles.startupInfo, handles.pseudoConsole);
|
||||||
|
handles.processInformation = new PROCESS_INFORMATION();
|
||||||
|
|
||||||
|
var status = ConPTYKernel32.INSTANCE.CreateProcess(null, quoted, null, null, false,
|
||||||
|
new DWORD(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT), toByteArray(envp), dir,
|
||||||
|
handles.startupInfo, handles.processInformation);
|
||||||
|
checkErr(status, "CreateProcess"); //$NON-NLS-1$
|
||||||
|
return getPID();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert envp to a byte array, encoding UTF_16LE. Remember to pass CREATE_UNICODE_ENVIRONMENT
|
||||||
|
* to CreateProcess
|
||||||
|
*/
|
||||||
|
public static Memory toByteArray(String[] envp) throws IOException {
|
||||||
|
if (envp == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
for (String string : envp) {
|
||||||
|
bos.write(string.getBytes(StandardCharsets.UTF_16LE));
|
||||||
|
// Terminate each variable with two zero bytes
|
||||||
|
bos.write(0);
|
||||||
|
bos.write(0);
|
||||||
|
}
|
||||||
|
// Terminate the whole block with two additional zero bytes
|
||||||
|
bos.write(0);
|
||||||
|
bos.write(0);
|
||||||
|
byte[] byteArray = bos.toByteArray();
|
||||||
|
Memory memory = new Memory(byteArray.length);
|
||||||
|
memory.write(0, byteArray, 0, byteArray.length);
|
||||||
|
return memory;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPID() {
|
||||||
|
handles.pid = handles.processInformation.dwProcessId.intValue();
|
||||||
|
return handles.pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Memory PrepareStartupInformation(ConPTYKernel32.STARTUPINFOEX pStartupInfo, HANDLEByReference phPC)
|
||||||
|
throws IOException {
|
||||||
|
pStartupInfo.StartupInfo.cb = new DWORD(pStartupInfo.size());
|
||||||
|
|
||||||
|
pStartupInfo.StartupInfo.hStdOutput = new HANDLE();
|
||||||
|
pStartupInfo.StartupInfo.hStdError = new HANDLE();
|
||||||
|
pStartupInfo.StartupInfo.hStdInput = new HANDLE();
|
||||||
|
pStartupInfo.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
|
||||||
|
|
||||||
|
boolean res;
|
||||||
|
|
||||||
|
var attrListSize = new ConPTYKernel32.SIZE_TByReference();
|
||||||
|
res = ConPTYKernel32.INSTANCE.InitializeProcThreadAttributeList(Pointer.NULL, new DWORD(1), new DWORD(0),
|
||||||
|
attrListSize);
|
||||||
|
Kernel32.INSTANCE.SetLastError(0);
|
||||||
|
var memory = new Memory(attrListSize.getValue().longValue());
|
||||||
|
|
||||||
|
res = ConPTYKernel32.INSTANCE.InitializeProcThreadAttributeList(memory, new DWORD(1), new DWORD(0),
|
||||||
|
attrListSize);
|
||||||
|
checkErr(res, "InitializeProcThreadAttributeList"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
var dwPROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = new DWORD_PTR(PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE);
|
||||||
|
|
||||||
|
res = ConPTYKernel32.INSTANCE.UpdateProcThreadAttribute(memory, new DWORD(0),
|
||||||
|
dwPROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, new PVOID(phPC.getValue().getPointer()),
|
||||||
|
new SIZE_T(Native.POINTER_SIZE), null, null);
|
||||||
|
checkErr(res, "UpdateProcThreadAttribute"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
pStartupInfo.lpAttributeList = memory.share(0);
|
||||||
|
return memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the contract of {@link Process#waitFor()}. This is used by {@link PTY#waitFor(org.eclipse.cdt.utils.spawner.Spawner, int),
|
||||||
|
* but in the Spawner case the PID is passed around unnecessarily. This method therefore waits for the process it created only,
|
||||||
|
* like how Process#waitFor() behaves.
|
||||||
|
*
|
||||||
|
* @see Process#waitFor()
|
||||||
|
* @see PTY#waitFor(org.eclipse.cdt.utils.spawner.Spawner, int)
|
||||||
|
*/
|
||||||
|
public int waitFor() {
|
||||||
|
try {
|
||||||
|
int what = 0;
|
||||||
|
HANDLE hProc;
|
||||||
|
|
||||||
|
hProc = Kernel32.INSTANCE.OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, false, getPID());
|
||||||
|
checkErr(hProc, "OpenProcess"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
what = Kernel32.INSTANCE.WaitForSingleObject(hProc, INFINITE);
|
||||||
|
|
||||||
|
IntByReference exit_code = new IntByReference(0);
|
||||||
|
if (what == WAIT_OBJECT_0) {
|
||||||
|
Kernel32.INSTANCE.GetExitCodeProcess(hProc, exit_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean closeHandle = Kernel32.INSTANCE.CloseHandle(hProc);
|
||||||
|
checkErr(closeHandle, "CloseHandle"); //$NON-NLS-1$
|
||||||
|
return exit_code.getValue();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Returning -1 is the equivalent of what was done
|
||||||
|
// in error handling in the JNI versions of waitFor
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the entire PTY session. This will have the side effect of closing all the IO
|
||||||
|
* channels at once. The process will also be terminated when the console is closed if
|
||||||
|
* it isn't closed already. If the console's host (conhost) is closed then the process
|
||||||
|
* won't be automatically terminated. This happens if conhost crashes and the behaviour
|
||||||
|
* with winpty is the same in that case.
|
||||||
|
*/
|
||||||
|
public synchronized void close() throws IOException {
|
||||||
|
if (handles == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean res;
|
||||||
|
|
||||||
|
res = ConPTYKernel32.INSTANCE.CloseHandle(handles.processInformation.hThread);
|
||||||
|
checkErr(res, "CloseHandle processInformation.hThread"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
res = ConPTYKernel32.INSTANCE.CloseHandle(handles.processInformation.hProcess);
|
||||||
|
checkErr(res, "CloseHandle processInformation.hProcess"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
ConPTYKernel32.INSTANCE.DeleteProcThreadAttributeList(handles.startupInfo.lpAttributeList);
|
||||||
|
handles.threadAttributeListMemory.clear();
|
||||||
|
|
||||||
|
ConPTYKernel32.INSTANCE.ClosePseudoConsole(handles.pseudoConsole.getValue());
|
||||||
|
|
||||||
|
res = ConPTYKernel32.INSTANCE.CancelIoEx(handles.pipeIn.getValue(), Pointer.NULL);
|
||||||
|
int err = Native.getLastError();
|
||||||
|
if (err != WinError.ERROR_NOT_FOUND) {
|
||||||
|
checkErr(res, "CancelIoEx"); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
res = ConPTYKernel32.INSTANCE.CloseHandle(handles.pipeOut.getValue());
|
||||||
|
checkErr(res, "CloseHandle pipeOut"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
res = ConPTYKernel32.INSTANCE.CloseHandle(handles.pipeIn.getValue());
|
||||||
|
checkErr(res, "CloseHandle pipeIn"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
handles = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements contract of {@link InputStream#read(byte[])}
|
||||||
|
* @see InputStream#read(byte[])
|
||||||
|
*/
|
||||||
|
public int read(byte[] buf) throws IOException {
|
||||||
|
if (handles == null) {
|
||||||
|
throw new IOException("ConPTY is closed."); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
var pipe = handles.pipeIn;
|
||||||
|
|
||||||
|
IntByReference dwBytesRead = new IntByReference(0);
|
||||||
|
boolean fRead = false;
|
||||||
|
|
||||||
|
fRead = Kernel32.INSTANCE.ReadFile(pipe.getValue(), buf, buf.length, dwBytesRead, null);
|
||||||
|
checkErr(fRead, "ReadFile"); //$NON-NLS-1$
|
||||||
|
int value = dwBytesRead.getValue();
|
||||||
|
if (value == 0) {
|
||||||
|
// We are at EOF because we are doing Synchronous and non-overlapped operation
|
||||||
|
// Implementation note: I don't know how to get this with terminal programs, so
|
||||||
|
// I have not seen this happen in development.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the contract of {@link OutputStream#write(byte[])}
|
||||||
|
* @see OutputStream#write(byte[])
|
||||||
|
*/
|
||||||
|
public void write(byte[] buf) throws IOException {
|
||||||
|
if (handles == null) {
|
||||||
|
throw new IOException("ConPTY is closed."); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
IntByReference dwBytesWritten = new IntByReference(0);
|
||||||
|
boolean fWritten = false;
|
||||||
|
fWritten = Kernel32.INSTANCE.WriteFile(handles.pipeOut.getValue(), buf, buf.length, dwBytesWritten, null);
|
||||||
|
checkErr(fWritten, "WriteFile"); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the contract of {@link PTY#setTerminalSize(int, int)}, but throws exceptions
|
||||||
|
* that PTY logs.
|
||||||
|
* @see PTY#setTerminalSize(int, int)
|
||||||
|
*/
|
||||||
|
public void setTerminalSize(int width, int height) throws IOException {
|
||||||
|
if (handles == null) {
|
||||||
|
throw new IOException("ConPTY is closed."); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
var consoleSize = new ConPTYKernel32.COORD_ByValue();
|
||||||
|
consoleSize.X = (short) width;
|
||||||
|
consoleSize.Y = (short) height;
|
||||||
|
|
||||||
|
HRESULT result = ConPTYKernel32.INSTANCE.ResizePseudoConsole(handles.pseudoConsole.getValue(), consoleSize);
|
||||||
|
checkErr(result, "ResizePseudoConsole"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw an IOException if hr is not S_OK.
|
||||||
|
*/
|
||||||
|
private static void checkErr(WinNT.HRESULT hr, String method) throws IOException {
|
||||||
|
if (!S_OK.equals(hr)) {
|
||||||
|
String msg = Kernel32Util.getLastErrorMessage();
|
||||||
|
throw new IOException(String.format("%s: %s", method, msg)); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw an IOException if status is false.
|
||||||
|
*/
|
||||||
|
private static void checkErr(boolean status, String method) throws IOException {
|
||||||
|
if (!status) {
|
||||||
|
int lastError = Native.getLastError();
|
||||||
|
String msg = Kernel32Util.formatMessage(lastError);
|
||||||
|
throw new IOException(String.format("%s: %s: %s", method, lastError, msg)); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw an IOException if handle is null.
|
||||||
|
*/
|
||||||
|
private static void checkErr(HANDLE handle, String method) throws IOException {
|
||||||
|
if (handle == null) {
|
||||||
|
String msg = Kernel32Util.getLastErrorMessage();
|
||||||
|
throw new IOException(String.format("%s: %s", method, msg)); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2021 Kichwa Coders Canada Inc. and others.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.eclipse.cdt.utils.pty;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noreference This class is not intended to be referenced by clients.
|
||||||
|
*/
|
||||||
|
public class ConPTYInputStream extends PTYInputStream {
|
||||||
|
|
||||||
|
private ConPTY conPty;
|
||||||
|
|
||||||
|
public ConPTYInputStream(ConPTY conPty) {
|
||||||
|
super(null);
|
||||||
|
this.conPty = conPty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see InputStream#read(byte[], int, int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buf, int off, int len) throws IOException {
|
||||||
|
if (buf == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
} else if ((off < 0) || (off > buf.length) || (len < 0) || ((off + len) > buf.length) || ((off + len) < 0)) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
} else if (len == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
byte[] tmpBuf = new byte[len];
|
||||||
|
|
||||||
|
len = conPty.read(tmpBuf);
|
||||||
|
if (len <= 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
System.arraycopy(tmpBuf, 0, buf, off, len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the Reader
|
||||||
|
* @exception IOException on error.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (conPty == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conPty.close();
|
||||||
|
} finally {
|
||||||
|
conPty = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2021 Kichwa Coders Canada Inc. and others.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.eclipse.cdt.utils.pty;
|
||||||
|
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
import com.sun.jna.platform.win32.Kernel32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is an extension of JNA and everything here needs to be contributed back
|
||||||
|
* to JNA. This class was written against JNA 5.6
|
||||||
|
*
|
||||||
|
* @noreference This interface is not intended to be referenced by clients.
|
||||||
|
* @noimplement This interface is not intended to be implemented by clients.
|
||||||
|
* @noextend This interface is not intended to be extended by clients.
|
||||||
|
*/
|
||||||
|
public interface ConPTYKernel32 extends Kernel32 {
|
||||||
|
ConPTYKernel32 INSTANCE = Native.load("kernel32", ConPTYKernel32.class, //$NON-NLS-1$
|
||||||
|
com.sun.jna.win32.W32APIOptions.DEFAULT_OPTIONS);
|
||||||
|
|
||||||
|
int PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016;
|
||||||
|
|
||||||
|
class SIZE_TByReference extends ULONG_PTRByReference {
|
||||||
|
@Override
|
||||||
|
public SIZE_T getValue() {
|
||||||
|
return new SIZE_T(super.getValue().longValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean CancelIoEx(HANDLE hFile, Pointer lpOverlapped);
|
||||||
|
|
||||||
|
public static class COORD_ByValue extends COORD implements Structure.ByValue {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Structure.FieldOrder({ "StartupInfo", "lpAttributeList" })
|
||||||
|
class STARTUPINFOEX extends Structure {
|
||||||
|
public STARTUPINFO StartupInfo;
|
||||||
|
public Pointer lpAttributeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT CreatePseudoConsole(COORD.ByValue size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags,
|
||||||
|
HANDLEByReference phPC);
|
||||||
|
|
||||||
|
HRESULT ResizePseudoConsole(HANDLE hPC, COORD.ByValue size);
|
||||||
|
|
||||||
|
void ClosePseudoConsole(HANDLE hPC);
|
||||||
|
|
||||||
|
boolean InitializeProcThreadAttributeList(Pointer lpAttributeList, DWORD dwAttributeCount, DWORD dwFlags,
|
||||||
|
SIZE_TByReference lpSize);
|
||||||
|
|
||||||
|
boolean UpdateProcThreadAttribute(Pointer lpAttributeList, DWORD dwFlags, DWORD_PTR Attribute, PVOID lpValue,
|
||||||
|
SIZE_T cbSize, PVOID lpPreviousValue, SIZE_TByReference lpReturnSize);
|
||||||
|
|
||||||
|
void DeleteProcThreadAttributeList(Pointer lpAttributeList);
|
||||||
|
|
||||||
|
boolean CreateProcess(String lpApplicationName, String lpCommandLine, SECURITY_ATTRIBUTES lpProcessAttributes,
|
||||||
|
SECURITY_ATTRIBUTES lpThreadAttributes, boolean bInheritHandles, DWORD dwCreationFlags,
|
||||||
|
Pointer lpEnvironment, String lpCurrentDirectory, STARTUPINFOEX lpStartupInfo,
|
||||||
|
PROCESS_INFORMATION lpProcessInformation);
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2021 Kichwa Coders Canada Inc. and others.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.eclipse.cdt.utils.pty;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noreference This class is not intended to be referenced by clients.
|
||||||
|
*/
|
||||||
|
public class ConPTYOutputStream extends PTYOutputStream {
|
||||||
|
private ConPTY conPty;
|
||||||
|
|
||||||
|
public ConPTYOutputStream(ConPTY conPty) {
|
||||||
|
super(null, false);
|
||||||
|
this.conPty = conPty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (b == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
} else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
} else if (len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
byte[] tmpBuf = new byte[len];
|
||||||
|
System.arraycopy(b, off, tmpBuf, 0, len);
|
||||||
|
conPty.write(tmpBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (conPty == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conPty.close();
|
||||||
|
} finally {
|
||||||
|
conPty = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Copyright (c) 2002, 2015 QNX Software Systems and others.
|
* Copyright (c) 2002, 2021 QNX Software Systems and others.
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials
|
* This program and the accompanying materials
|
||||||
* are made available under the terms of the Eclipse Public License 2.0
|
* are made available under the terms of the Eclipse Public License 2.0
|
||||||
|
@ -40,19 +40,36 @@ public class PTY {
|
||||||
}
|
}
|
||||||
|
|
||||||
final Mode mode;
|
final Mode mode;
|
||||||
|
/**
|
||||||
|
* Unused in conPTY.
|
||||||
|
* Created, but never read in winPTY.
|
||||||
|
* Important for Posix PTY.
|
||||||
|
*/
|
||||||
final String slave;
|
final String slave;
|
||||||
final PTYInputStream in;
|
final PTYInputStream in;
|
||||||
final PTYOutputStream out;
|
final PTYOutputStream out;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTE: Field is accessed by the native layer. Do not refactor!
|
* NOTE: Field is accessed by the native layer. Do not refactor!
|
||||||
|
* This field is NOT used by ConPTY layer.
|
||||||
*/
|
*/
|
||||||
int master;
|
int master;
|
||||||
|
|
||||||
private static boolean hasPTY;
|
private static boolean hasPTY;
|
||||||
|
|
||||||
|
private enum IS_CONPTY {
|
||||||
|
CONPTY_UNKNOWN, CONPTY_YES, CONPTY_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We don't know if we have conpty until we try - so this starts
|
||||||
|
* null and will be true or false once it is determined.
|
||||||
|
*/
|
||||||
|
private static IS_CONPTY isConPTY = IS_CONPTY.CONPTY_UNKNOWN;
|
||||||
private static boolean isWinPTY;
|
private static boolean isWinPTY;
|
||||||
private static boolean isConsoleModeSupported;
|
private static boolean isConsoleModeSupported;
|
||||||
private static boolean setTerminalSizeErrorAlreadyLogged;
|
private static boolean setTerminalSizeErrorAlreadyLogged;
|
||||||
|
private ConPTY conPTY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The master fd is used on two streams. We need to wrap the fd
|
* The master fd is used on two streams. We need to wrap the fd
|
||||||
|
@ -119,14 +136,40 @@ public class PTY {
|
||||||
if (isConsole() && !isConsoleModeSupported) {
|
if (isConsole() && !isConsoleModeSupported) {
|
||||||
throw new IOException(Messages.Util_exception_cannotCreatePty);
|
throw new IOException(Messages.Util_exception_cannotCreatePty);
|
||||||
}
|
}
|
||||||
slave = hasPTY ? openMaster(isConsole()) : null;
|
PTYInputStream inInit = null;
|
||||||
|
PTYOutputStream outInit = null;
|
||||||
if (slave == null) {
|
String slaveInit = null;
|
||||||
throw new IOException(Messages.Util_exception_cannotCreatePty);
|
if (isConPTY != IS_CONPTY.CONPTY_NO) {
|
||||||
|
try {
|
||||||
|
conPTY = new ConPTY();
|
||||||
|
isConPTY = IS_CONPTY.CONPTY_YES;
|
||||||
|
slaveInit = "conpty"; //$NON-NLS-1$
|
||||||
|
inInit = new ConPTYInputStream(conPTY);
|
||||||
|
outInit = new ConPTYOutputStream(conPTY);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
isConPTY = IS_CONPTY.CONPTY_NO;
|
||||||
|
CNativePlugin.log(Messages.PTY_FailedToStartConPTY, e);
|
||||||
|
} catch (NoClassDefFoundError e) {
|
||||||
|
isConPTY = IS_CONPTY.CONPTY_NO;
|
||||||
|
CNativePlugin.log(Messages.PTY_NoClassDefFoundError, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
in = new PTYInputStream(new MasterFD());
|
// fall through in exception case as well as normal non-conPTY
|
||||||
out = new PTYOutputStream(new MasterFD(), !isWinPTY);
|
if (isConPTY == IS_CONPTY.CONPTY_NO) {
|
||||||
|
|
||||||
|
slaveInit = hasPTY ? openMaster(isConsole()) : null;
|
||||||
|
|
||||||
|
if (slaveInit == null) {
|
||||||
|
throw new IOException(Messages.Util_exception_cannotCreatePty);
|
||||||
|
}
|
||||||
|
|
||||||
|
inInit = new PTYInputStream(new MasterFD());
|
||||||
|
outInit = new PTYOutputStream(new MasterFD(), !isWinPTY);
|
||||||
|
}
|
||||||
|
slave = slaveInit;
|
||||||
|
in = inInit;
|
||||||
|
out = outInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -185,12 +228,17 @@ public class PTY {
|
||||||
*/
|
*/
|
||||||
public final void setTerminalSize(int width, int height) {
|
public final void setTerminalSize(int width, int height) {
|
||||||
try {
|
try {
|
||||||
change_window_size(master, width, height);
|
if (isConPTY == IS_CONPTY.CONPTY_YES) {
|
||||||
} catch (UnsatisfiedLinkError ule) {
|
conPTY.setTerminalSize(width, height);
|
||||||
|
} else {
|
||||||
|
change_window_size(master, width, height);
|
||||||
|
}
|
||||||
|
} catch (UnsatisfiedLinkError | IOException e) {
|
||||||
if (!setTerminalSizeErrorAlreadyLogged) {
|
if (!setTerminalSizeErrorAlreadyLogged) {
|
||||||
setTerminalSizeErrorAlreadyLogged = true;
|
setTerminalSizeErrorAlreadyLogged = true;
|
||||||
CNativePlugin.log(Messages.Util_exception_cannotSetTerminalSize, ule);
|
CNativePlugin.log(Messages.Util_exception_cannotSetTerminalSize, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +248,9 @@ public class PTY {
|
||||||
*/
|
*/
|
||||||
public int exec_pty(Spawner spawner, String[] cmdarray, String[] envp, String dir, IChannel[] chan)
|
public int exec_pty(Spawner spawner, String[] cmdarray, String[] envp, String dir, IChannel[] chan)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (isWinPTY) {
|
if (isConPTY == IS_CONPTY.CONPTY_YES) {
|
||||||
|
return conPTY.exec(cmdarray, envp, dir);
|
||||||
|
} else if (isWinPTY) {
|
||||||
return exec2(cmdarray, envp, dir, chan, slave, master, isConsole());
|
return exec2(cmdarray, envp, dir, chan, slave, master, isConsole());
|
||||||
} else {
|
} else {
|
||||||
return spawner.exec2(cmdarray, envp, dir, chan, slave, master, isConsole());
|
return spawner.exec2(cmdarray, envp, dir, chan, slave, master, isConsole());
|
||||||
|
@ -212,7 +262,9 @@ public class PTY {
|
||||||
* @since 5.6
|
* @since 5.6
|
||||||
*/
|
*/
|
||||||
public int waitFor(Spawner spawner, int pid) {
|
public int waitFor(Spawner spawner, int pid) {
|
||||||
if (isWinPTY) {
|
if (isConPTY == IS_CONPTY.CONPTY_YES) {
|
||||||
|
return conPTY.waitFor();
|
||||||
|
} else if (isWinPTY) {
|
||||||
return waitFor(master, pid);
|
return waitFor(master, pid);
|
||||||
} else {
|
} else {
|
||||||
return spawner.waitFor(pid);
|
return spawner.waitFor(pid);
|
||||||
|
@ -236,8 +288,19 @@ public class PTY {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
isWinPTY = Platform.OS_WIN32.equals(Platform.getOS());
|
boolean isWindows = Platform.OS_WIN32.equals(Platform.getOS());
|
||||||
if (isWinPTY) {
|
if (!isWindows) {
|
||||||
|
isConPTY = IS_CONPTY.CONPTY_NO;
|
||||||
|
}
|
||||||
|
// Force conpty off by default
|
||||||
|
// NOTE: to invert the default, the presence of the property must be checked too, not
|
||||||
|
// just the getBoolean return!
|
||||||
|
if (!Boolean.getBoolean("org.eclipse.cdt.core.winpty_console_mode")) { //$NON-NLS-1$
|
||||||
|
isConPTY = IS_CONPTY.CONPTY_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
isWinPTY = isWindows;
|
||||||
|
if (isWindows) {
|
||||||
// When we used to build with VC++ we used DelayLoadDLLs (See Gerrit 167674 and Bug 521515) so that the winpty
|
// When we used to build with VC++ we used DelayLoadDLLs (See Gerrit 167674 and Bug 521515) so that the winpty
|
||||||
// could be found. When we ported to mingw we didn't port across this feature because it was simpler to just
|
// could be found. When we ported to mingw we didn't port across this feature because it was simpler to just
|
||||||
// manually load winpty first.
|
// manually load winpty first.
|
||||||
|
|
|
@ -2,7 +2,7 @@ Manifest-Version: 1.0
|
||||||
Bundle-ManifestVersion: 2
|
Bundle-ManifestVersion: 2
|
||||||
Bundle-Name: %pluginName
|
Bundle-Name: %pluginName
|
||||||
Bundle-SymbolicName: org.eclipse.tm.terminal.view.ui;singleton:=true
|
Bundle-SymbolicName: org.eclipse.tm.terminal.view.ui;singleton:=true
|
||||||
Bundle-Version: 4.8.100.qualifier
|
Bundle-Version: 4.9.0.qualifier
|
||||||
Bundle-Activator: org.eclipse.tm.terminal.view.ui.activator.UIPlugin
|
Bundle-Activator: org.eclipse.tm.terminal.view.ui.activator.UIPlugin
|
||||||
Bundle-Vendor: %providerName
|
Bundle-Vendor: %providerName
|
||||||
Require-Bundle: org.eclipse.core.expressions;bundle-version="3.4.400",
|
Require-Bundle: org.eclipse.core.expressions;bundle-version="3.4.400",
|
||||||
|
|
|
@ -160,6 +160,19 @@ public abstract class AbstractStreamsConnector extends TerminalConnectorImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doDisconnect() {
|
protected void doDisconnect() {
|
||||||
|
// First let all the monitors know they are about to be closed, this allows them
|
||||||
|
// to suppress errors if closing one stream causes other streams to all close
|
||||||
|
// as a side effect.
|
||||||
|
if (stdInMonitor != null) {
|
||||||
|
stdInMonitor.disposalComing();
|
||||||
|
}
|
||||||
|
if (stdOutMonitor != null) {
|
||||||
|
stdOutMonitor.disposalComing();
|
||||||
|
}
|
||||||
|
if (stdErrMonitor != null) {
|
||||||
|
stdErrMonitor.disposalComing();
|
||||||
|
}
|
||||||
|
|
||||||
// Dispose the streams
|
// Dispose the streams
|
||||||
if (stdInMonitor != null) {
|
if (stdInMonitor != null) {
|
||||||
stdInMonitor.dispose();
|
stdInMonitor.dispose();
|
||||||
|
|
|
@ -81,6 +81,11 @@ public class InputStreamMonitor extends OutputStream implements IDisposable {
|
||||||
|
|
||||||
private int replacement;
|
private int replacement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #disposalComing()
|
||||||
|
*/
|
||||||
|
private boolean disposalComing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
@ -154,6 +159,8 @@ public class InputStreamMonitor extends OutputStream implements IDisposable {
|
||||||
if (disposed)
|
if (disposed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
disposalComing();
|
||||||
|
|
||||||
// Mark the monitor disposed
|
// Mark the monitor disposed
|
||||||
disposed = true;
|
disposed = true;
|
||||||
|
|
||||||
|
@ -250,7 +257,7 @@ public class InputStreamMonitor extends OutputStream implements IDisposable {
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// IOException received. If this is happening when already disposed -> ignore
|
// IOException received. If this is happening when already disposed -> ignore
|
||||||
if (!disposed) {
|
if (!disposed && !disposalComing) {
|
||||||
IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(),
|
IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(),
|
||||||
NLS.bind(Messages.InputStreamMonitor_error_writingToStream, e.getLocalizedMessage()),
|
NLS.bind(Messages.InputStreamMonitor_error_writingToStream, e.getLocalizedMessage()),
|
||||||
e);
|
e);
|
||||||
|
@ -355,4 +362,13 @@ public class InputStreamMonitor extends OutputStream implements IDisposable {
|
||||||
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the receiver that the stream is about to be closed. This allows the stream to suppress error messages
|
||||||
|
* that are side effects of the asynchronous nature of the stream closing.
|
||||||
|
* @since 4.9
|
||||||
|
*/
|
||||||
|
public void disposalComing() {
|
||||||
|
disposalComing = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,11 @@ public class OutputStreamMonitor implements IDisposable {
|
||||||
// The list of registered listener
|
// The list of registered listener
|
||||||
private final ListenerList<ITerminalServiceOutputStreamMonitorListener> listeners;
|
private final ListenerList<ITerminalServiceOutputStreamMonitorListener> listeners;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #disposalComing()
|
||||||
|
*/
|
||||||
|
private boolean disposalComing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
@ -132,6 +137,8 @@ public class OutputStreamMonitor implements IDisposable {
|
||||||
if (disposed)
|
if (disposed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
disposalComing();
|
||||||
|
|
||||||
// Mark the monitor disposed
|
// Mark the monitor disposed
|
||||||
disposed = true;
|
disposed = true;
|
||||||
|
|
||||||
|
@ -208,7 +215,7 @@ public class OutputStreamMonitor implements IDisposable {
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// IOException received. If this is happening when already disposed -> ignore
|
// IOException received. If this is happening when already disposed -> ignore
|
||||||
if (!disposed) {
|
if (!disposed && !disposalComing) {
|
||||||
IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(),
|
IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(),
|
||||||
NLS.bind(Messages.OutputStreamMonitor_error_readingFromStream, e.getLocalizedMessage()), e);
|
NLS.bind(Messages.OutputStreamMonitor_error_readingFromStream, e.getLocalizedMessage()), e);
|
||||||
UIPlugin.getDefault().getLog().log(status);
|
UIPlugin.getDefault().getLog().log(status);
|
||||||
|
@ -217,7 +224,7 @@ public class OutputStreamMonitor implements IDisposable {
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
// killing the stream monitor while reading can cause an NPE
|
// killing the stream monitor while reading can cause an NPE
|
||||||
// when reading from the stream
|
// when reading from the stream
|
||||||
if (!disposed && thread != null) {
|
if (!disposed && thread != null && !disposalComing) {
|
||||||
IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(),
|
IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(),
|
||||||
NLS.bind(Messages.OutputStreamMonitor_error_readingFromStream, e.getLocalizedMessage()), e);
|
NLS.bind(Messages.OutputStreamMonitor_error_readingFromStream, e.getLocalizedMessage()), e);
|
||||||
UIPlugin.getDefault().getLog().log(status);
|
UIPlugin.getDefault().getLog().log(status);
|
||||||
|
@ -318,4 +325,13 @@ public class OutputStreamMonitor implements IDisposable {
|
||||||
|
|
||||||
return byteBuffer;
|
return byteBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the receiver that the stream is about to be closed. This allows the stream to suppress error messages
|
||||||
|
* that are side effects of the asynchronous nature of the stream closing.
|
||||||
|
* @since 4.9
|
||||||
|
*/
|
||||||
|
public void disposalComing() {
|
||||||
|
disposalComing = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue