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
diff --git a/core/org.eclipse.cdt.core.native/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core.native/META-INF/MANIFEST.MF
index f5e9bc3..95b98ef 100644
--- a/core/org.eclipse.cdt.core.native/META-INF/MANIFEST.MF
+++ b/core/org.eclipse.cdt.core.native/META-INF/MANIFEST.MF
@@ -10,7 +10,9 @@
  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
diff --git a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/internal/core/natives/Messages.java b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/internal/core/natives/Messages.java
index 5874288e..b72519e 100644
--- a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/internal/core/natives/Messages.java
+++ b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/internal/core/natives/Messages.java
@@ -16,6 +16,8 @@
 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;
diff --git a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/internal/core/natives/Messages.properties b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/internal/core/natives/Messages.properties
index 9eb988a..8e91c7b 100644
--- a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/internal/core/natives/Messages.properties
+++ b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/internal/core/natives/Messages.properties
@@ -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
diff --git a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/WindowsArgumentQuoter.java b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/WindowsArgumentQuoter.java
new file mode 100644
index 0000000..fcfda8ed
--- /dev/null
+++ b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/WindowsArgumentQuoter.java
@@ -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('"');
+		}
+	}
+
+}
diff --git a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTY.java b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTY.java
new file mode 100644
index 0000000..ec57782
--- /dev/null
+++ b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTY.java
@@ -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$
+		}
+	}
+}
diff --git a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTYInputStream.java b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTYInputStream.java
new file mode 100644
index 0000000..3274ec5
--- /dev/null
+++ b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTYInputStream.java
@@ -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;
+		}
+
+	}
+
+}
diff --git a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTYKernel32.java b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTYKernel32.java
new file mode 100644
index 0000000..bbcdb2d
--- /dev/null
+++ b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTYKernel32.java
@@ -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);
+}
\ No newline at end of file
diff --git a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTYOutputStream.java b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTYOutputStream.java
new file mode 100644
index 0000000..5d6239d
--- /dev/null
+++ b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/ConPTYOutputStream.java
@@ -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;
+		}
+	}
+}
diff --git a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/PTY.java b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/PTY.java
index 9eabae7..2774902 100644
--- a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/PTY.java
+++ b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/PTY.java
@@ -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 @@
 	}
 
 	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 @@
 		if (isConsole() && !isConsoleModeSupported) {
 			throw new IOException(Messages.Util_exception_cannotCreatePty);
 		}
-		slave = hasPTY ? openMaster(isConsole()) : null;
-
-		if (slave == null) {
-			throw new IOException(Messages.Util_exception_cannotCreatePty);
+		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);
+			}
 		}
 
-		in = new PTYInputStream(new MasterFD());
-		out = new PTYOutputStream(new MasterFD(), !isWinPTY);
+		// 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);
+			}
+
+			inInit = new PTYInputStream(new MasterFD());
+			outInit = new PTYOutputStream(new MasterFD(), !isWinPTY);
+		}
+		slave = slaveInit;
+		in = inInit;
+		out = outInit;
 	}
 
 	/**
@@ -185,12 +228,17 @@
 	 */
 	public final void setTerminalSize(int width, int height) {
 		try {
-			change_window_size(master, width, height);
-		} catch (UnsatisfiedLinkError ule) {
+			if (isConPTY == IS_CONPTY.CONPTY_YES) {
+				conPTY.setTerminalSize(width, height);
+			} else {
+				change_window_size(master, width, height);
+			}
+		} 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 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 @@
 	 * @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 @@
 
 	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.
diff --git a/terminal/plugins/org.eclipse.tm.terminal.view.ui/META-INF/MANIFEST.MF b/terminal/plugins/org.eclipse.tm.terminal.view.ui/META-INF/MANIFEST.MF
index d5a95f4..a8945c5 100644
--- a/terminal/plugins/org.eclipse.tm.terminal.view.ui/META-INF/MANIFEST.MF
+++ b/terminal/plugins/org.eclipse.tm.terminal.view.ui/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 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",
diff --git a/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/AbstractStreamsConnector.java b/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/AbstractStreamsConnector.java
index f687c3c..686c706 100644
--- a/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/AbstractStreamsConnector.java
+++ b/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/AbstractStreamsConnector.java
@@ -160,6 +160,19 @@
 
 	@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();
diff --git a/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/InputStreamMonitor.java b/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/InputStreamMonitor.java
index e99c24f..3713ee3 100644
--- a/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/InputStreamMonitor.java
+++ b/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/InputStreamMonitor.java
@@ -82,6 +82,11 @@
 	private int replacement;
 
 	/**
+	 * @see #disposalComing()
+	 */
+	private boolean disposalComing;
+
+	/**
 	 * Constructor.
 	 *
 	 * @param terminalControl The parent terminal control. Must not be <code>null</code>.
@@ -154,6 +159,8 @@
 		if (disposed)
 			return;
 
+		disposalComing();
+
 		// Mark the monitor disposed
 		disposed = true;
 
@@ -250,7 +257,7 @@
 					}
 				} 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 @@
 
 		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;
+	}
 }
diff --git a/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/OutputStreamMonitor.java b/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/OutputStreamMonitor.java
index 1772f5d..2362160 100644
--- a/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/OutputStreamMonitor.java
+++ b/terminal/plugins/org.eclipse.tm.terminal.view.ui/src/org/eclipse/tm/terminal/view/ui/streams/OutputStreamMonitor.java
@@ -64,6 +64,11 @@
 	private final ListenerList<ITerminalServiceOutputStreamMonitorListener> listeners;
 
 	/**
+	 * @see #disposalComing()
+	 */
+	private boolean disposalComing;
+
+	/**
 	 * Constructor.
 	 *
 	 * @param terminalControl The parent terminal control. Must not be <code>null</code>.
@@ -132,6 +137,8 @@
 		if (disposed)
 			return;
 
+		disposalComing();
+
 		// Mark the monitor disposed
 		disposed = true;
 
@@ -208,7 +215,7 @@
 				}
 			} 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 @@
 			} 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 @@
 
 		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;
+	}
 }