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.pty;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-RequiredExecutionEnvironment: JavaSE-11
|
||||
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;
|
||||
|
||||
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_cannotSetTerminalSize;
|
||||
public static String Util_error_cannotRun;
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
# 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_closeError=close error
|
||||
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
|
||||
* are made available under the terms of the Eclipse Public License 2.0
|
||||
|
@ -40,19 +40,36 @@ public class PTY {
|
|||
}
|
||||
|
||||
final Mode mode;
|
||||
/**
|
||||
* Unused in conPTY.
|
||||
* Created, but never read in winPTY.
|
||||
* Important for Posix PTY.
|
||||
*/
|
||||
final String slave;
|
||||
final PTYInputStream in;
|
||||
final PTYOutputStream out;
|
||||
|
||||
/**
|
||||
* NOTE: Field is accessed by the native layer. Do not refactor!
|
||||
* This field is NOT used by ConPTY layer.
|
||||
*/
|
||||
int master;
|
||||
|
||||
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 isConsoleModeSupported;
|
||||
private static boolean setTerminalSizeErrorAlreadyLogged;
|
||||
private ConPTY conPTY;
|
||||
|
||||
/**
|
||||
* The master fd is used on two streams. We need to wrap the fd
|
||||
|
@ -119,14 +136,40 @@ public class PTY {
|
|||
if (isConsole() && !isConsoleModeSupported) {
|
||||
throw new IOException(Messages.Util_exception_cannotCreatePty);
|
||||
}
|
||||
slave = hasPTY ? openMaster(isConsole()) : null;
|
||||
PTYInputStream inInit = null;
|
||||
PTYOutputStream outInit = null;
|
||||
String slaveInit = null;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (slave == null) {
|
||||
// fall through in exception case as well as normal non-conPTY
|
||||
if (isConPTY == IS_CONPTY.CONPTY_NO) {
|
||||
|
||||
slaveInit = hasPTY ? openMaster(isConsole()) : null;
|
||||
|
||||
if (slaveInit == null) {
|
||||
throw new IOException(Messages.Util_exception_cannotCreatePty);
|
||||
}
|
||||
|
||||
in = new PTYInputStream(new MasterFD());
|
||||
out = new PTYOutputStream(new MasterFD(), !isWinPTY);
|
||||
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) {
|
||||
try {
|
||||
if (isConPTY == IS_CONPTY.CONPTY_YES) {
|
||||
conPTY.setTerminalSize(width, height);
|
||||
} else {
|
||||
change_window_size(master, width, height);
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
}
|
||||
} catch (UnsatisfiedLinkError | IOException e) {
|
||||
if (!setTerminalSizeErrorAlreadyLogged) {
|
||||
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)
|
||||
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());
|
||||
} else {
|
||||
return spawner.exec2(cmdarray, envp, dir, chan, slave, master, isConsole());
|
||||
|
@ -212,7 +262,9 @@ public class PTY {
|
|||
* @since 5.6
|
||||
*/
|
||||
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);
|
||||
} else {
|
||||
return spawner.waitFor(pid);
|
||||
|
@ -236,8 +288,19 @@ public class PTY {
|
|||
|
||||
static {
|
||||
try {
|
||||
isWinPTY = Platform.OS_WIN32.equals(Platform.getOS());
|
||||
if (isWinPTY) {
|
||||
boolean isWindows = Platform.OS_WIN32.equals(Platform.getOS());
|
||||
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
|
||||
// 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.
|
||||
|
|
|
@ -2,7 +2,7 @@ Manifest-Version: 1.0
|
|||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: %pluginName
|
||||
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-Vendor: %providerName
|
||||
Require-Bundle: org.eclipse.core.expressions;bundle-version="3.4.400",
|
||||
|
|
|
@ -160,6 +160,19 @@ public abstract class AbstractStreamsConnector extends TerminalConnectorImpl {
|
|||
|
||||
@Override
|
||||
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
|
||||
if (stdInMonitor != null) {
|
||||
stdInMonitor.dispose();
|
||||
|
|
|
@ -81,6 +81,11 @@ public class InputStreamMonitor extends OutputStream implements IDisposable {
|
|||
|
||||
private int replacement;
|
||||
|
||||
/**
|
||||
* @see #disposalComing()
|
||||
*/
|
||||
private boolean disposalComing;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
|
@ -154,6 +159,8 @@ public class InputStreamMonitor extends OutputStream implements IDisposable {
|
|||
if (disposed)
|
||||
return;
|
||||
|
||||
disposalComing();
|
||||
|
||||
// Mark the monitor disposed
|
||||
disposed = true;
|
||||
|
||||
|
@ -250,7 +257,7 @@ public class InputStreamMonitor extends OutputStream implements IDisposable {
|
|||
}
|
||||
} catch (IOException e) {
|
||||
// IOException received. If this is happening when already disposed -> ignore
|
||||
if (!disposed) {
|
||||
if (!disposed && !disposalComing) {
|
||||
IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(),
|
||||
NLS.bind(Messages.InputStreamMonitor_error_writingToStream, e.getLocalizedMessage()),
|
||||
e);
|
||||
|
@ -355,4 +362,13 @@ public class InputStreamMonitor extends OutputStream implements IDisposable {
|
|||
|
||||
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
|
||||
private final ListenerList<ITerminalServiceOutputStreamMonitorListener> listeners;
|
||||
|
||||
/**
|
||||
* @see #disposalComing()
|
||||
*/
|
||||
private boolean disposalComing;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
|
@ -132,6 +137,8 @@ public class OutputStreamMonitor implements IDisposable {
|
|||
if (disposed)
|
||||
return;
|
||||
|
||||
disposalComing();
|
||||
|
||||
// Mark the monitor disposed
|
||||
disposed = true;
|
||||
|
||||
|
@ -208,7 +215,7 @@ public class OutputStreamMonitor implements IDisposable {
|
|||
}
|
||||
} catch (IOException e) {
|
||||
// IOException received. If this is happening when already disposed -> ignore
|
||||
if (!disposed) {
|
||||
if (!disposed && !disposalComing) {
|
||||
IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(),
|
||||
NLS.bind(Messages.OutputStreamMonitor_error_readingFromStream, e.getLocalizedMessage()), e);
|
||||
UIPlugin.getDefault().getLog().log(status);
|
||||
|
@ -217,7 +224,7 @@ public class OutputStreamMonitor implements IDisposable {
|
|||
} catch (NullPointerException e) {
|
||||
// killing the stream monitor while reading can cause an NPE
|
||||
// when reading from the stream
|
||||
if (!disposed && thread != null) {
|
||||
if (!disposed && thread != null && !disposalComing) {
|
||||
IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(),
|
||||
NLS.bind(Messages.OutputStreamMonitor_error_readingFromStream, e.getLocalizedMessage()), e);
|
||||
UIPlugin.getDefault().getLog().log(status);
|
||||
|
@ -318,4 +325,13 @@ public class OutputStreamMonitor implements IDisposable {
|
|||
|
||||
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