diff --git a/src/configure.in b/src/configure.in
index dd0f0dfa9bb0e506c00888733fa10ed54355c1e1..11fa4cf5ec7b12451374e17f4c10749121a319da 100644
--- a/src/configure.in
+++ b/src/configure.in
@@ -3310,9 +3310,17 @@ AC_CHECK_HEADERS(winsock2.h sys/rusage.h time.h sys/time.h sys/types.h \
 #ifdef HAVE_SYS_PARAM_H
 # include <sys/param.h>
 #endif
+/* Necessary to find struct winsize. */
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#elif defined(HAVE_SYS_TERMIOS_H)
+/* NB: Deprecated by <termios.h> above. */
+#include <sys/termios.h>
+#endif
 ])
 
-AC_CHECK_TYPES([HPCON, LPPROC_THREAD_ATTRIBUTE_LIST, LPSTARTUPINFOEXW],,,[
+AC_CHECK_TYPES([HPCON, LPPROC_THREAD_ATTRIBUTE_LIST, LPSTARTUPINFOEXW,
+                struct winsize],,,[
 #if (defined(__WINNT__) || defined(__WIN32__)) && !defined(__NT__)
 #define __NT__
 #endif
@@ -4578,6 +4586,7 @@ AC_CHECK_FUNCS( \
  setsid \
  initgroups setgroups \
  socketpair \
+ openpty \
  bswap16 \
  __bswap16 \
  bswap32 \
diff --git a/src/fdlib.c b/src/fdlib.c
index d25465ca4ee5dcc502b7d359ad0de256f016722e..e27dfc6254923a204e9f0b67ce96fd6123f2ca04 100644
--- a/src/fdlib.c
+++ b/src/fdlib.c
@@ -46,6 +46,7 @@
 #endif
 
 #include "threads.h"
+#include "signal_handler.h"
 
 /* Mutex protecting da_handle, fd_type, fd_busy and first_free_handle. */
 static MUTEX_T fd_mutex;
@@ -73,6 +74,9 @@ HANDLE da_handle[FD_SETSIZE];
  *
  *   FD_PIPE (-5)
  *     Handle from CreatePipe().
+ *
+ *   FD_PTY (-6)
+ *     struct my_pty * set by openpty().
  */
 int fd_type[FD_SETSIZE];
 
@@ -264,11 +268,82 @@ PMOD_EXPORT void set_errno_from_win32_error (unsigned long err)
 
 #undef NTLIBFUNC
 #define NTLIBFUNC(LIB, RET, NAME, ARGLIST)				\
-  typedef RET (WINAPI * PIKE_CONCAT3(Pike_NT_, NAME, _type)) ARGLIST;	\
-  static PIKE_CONCAT3(Pike_NT_, NAME, _type) PIKE_CONCAT(Pike_NT_, NAME)
+  PIKE_CONCAT3(Pike_NT_, NAME, _type) PIKE_CONCAT(Pike_NT_, NAME)
 
 #include "ntlibfuncs.h"
 
+/* PTY-handling
+ *
+ * PTYs on NT are implemented via ConPTY (Windows 10 1809 (build 17763)
+ * and later).
+ *
+ * We attempt to have them behave similar to ptys on POSIX-systems.
+ * This does however require a bit of work.
+ *
+ * Ptys are allocated as a pair of pipes (one in each direction)
+ * between master and slave. The slave end is given to ConPTY.
+ *
+ * When a process is started that uses a slave for stdin, stdout
+ * or stderr, it will instead of the slave pipes be registered
+ * with the ConPTY, which in turn will provide the actual pty handles.
+ *
+ * Caveat: The ConPTY does not terminate its end of the pipes
+ *         when all client processes have closed their handles.
+ *         This is due to it still being possible to use the
+ *         ConPTY for further processes.
+ *
+ * Update: The above behavior is apparently a bug. Cf
+ *         https://github.com/microsoft/terminal/issues/4564
+ *
+ * Note also that closing the ConPTY causes any remaining client
+ * processes to be terminated.
+ *
+ * In order to handle the above, we keep track of the processes
+ * that have been registered with the ConPTY, and use this list
+ * to close the ConPTY when all the processes are dead and
+ * we do not have the slave end any more.
+ */
+
+void free_pty(struct my_pty *pty)
+{
+  if (sub_ref(pty)) return;
+  free(pty);
+}
+
+/* NB: fd_mutex MUST be held at entry. */
+static void close_pty(struct my_pty *pty)
+{
+  struct my_pty *other;
+  struct pid_status *pid;
+
+  if (--pty->fd_refs) {
+    sub_ref(pty);
+    return;
+  }
+
+  CloseHandle(pty->write_pipe);
+  CloseHandle(pty->read_pipe);
+
+  /* Unlink the pair. */
+  other = pty->other;
+  if (other) {
+    other->other = NULL;
+    pty->other = NULL;
+  }
+
+  if (pty->conpty) {
+    /* Closing the master side. */
+    Pike_NT_ClosePseudoConsole(pty->conpty);
+    pty->conpty = 0;
+  }
+
+  while ((pid = pty->clients)) {
+    pty->clients = pid_status_unlink_pty(pid);
+  }
+
+  free_pty(pty);
+}
+
 #define ISSEPARATOR(a) ((a) == '\\' || (a) == '/')
 
 #ifdef PIKE_DEBUG
@@ -468,6 +543,9 @@ static int reallocate_fd(int fd, int type, HANDLE handle)
   if (fd_type[fd] == FD_SOCKET) {
     FDDEBUG(fprintf(stderr, "Closing socket that was fd %d.\n", fd));
     closesocket((SOCKET)da_handle[fd]);
+  } else if (fd_type[fd] == FD_PTY) {
+    FDDEBUG(fprintf(stderr, "Closing pty that was fd %d.\n", fd));
+    close_pty((struct my_pty *)da_handle[fd]);
   } else {
     FDDEBUG(fprintf(stderr, "Closing handle that was fd %d.\n", fd));
     CloseHandle(da_handle[fd]);
@@ -562,6 +640,7 @@ PMOD_EXPORT char *debug_fd_info(int fd)
     case FD_CONSOLE: return "IS CONSOLE";
     case FD_FILE: return "IS FILE";
     case FD_PIPE: return "IS PIPE";
+    case FD_PTY: return "IS PTY";
     default: return "NOT OPEN";
   }
 }
@@ -588,6 +667,9 @@ PMOD_EXPORT int debug_fd_query_properties(int fd, int guess)
 
     case FD_PIPE:
       return fd_INTERPROCESSABLE | fd_BUFFERED;
+
+    case FD_PTY:
+      return fd_INTERPROCESSABLE | fd_BUFFERED | fd_BIDIRECTIONAL;
     default: return 0;
   }
 }
@@ -2003,6 +2085,14 @@ PMOD_EXPORT int debug_fd_close(FD fd)
       }
       break;
 
+    case FD_PTY:
+      {
+	mt_lock(&fd_mutex);
+	close_pty((struct my_pty *)h);
+	mt_unlock(&fd_mutex);
+      }
+      break;
+
     default:
       if(!CloseHandle(h))
       {
@@ -2051,6 +2141,13 @@ PMOD_EXPORT ptrdiff_t debug_fd_write(FD fd, void *buf, ptrdiff_t len)
 	return ret;
       }
 
+    case FD_PTY:
+      {
+	struct my_pty *pty = (struct my_pty *)handle;
+	handle = pty->write_pipe;
+      }
+      /* FALLTHRU */
+
     case FD_CONSOLE:
     case FD_FILE:
     case FD_PIPE:
@@ -2089,7 +2186,7 @@ PMOD_EXPORT ptrdiff_t debug_fd_read(FD fd, void *to, ptrdiff_t len)
   if (fd_to_handle(fd, &type, &handle, 0) < 0) return -1;
 
   FDDEBUG(fprintf(stderr,"Reading %d bytes from %d (%d) to %lx\n",
-		  len, fd, PTRDIFF_T_TO_LONG((ptrdiff_t)h),
+		  len, fd, PTRDIFF_T_TO_LONG((ptrdiff_t)handle),
 		  PTRDIFF_T_TO_LONG((ptrdiff_t)to)));
 
   switch(type)
@@ -2108,6 +2205,22 @@ PMOD_EXPORT ptrdiff_t debug_fd_read(FD fd, void *to, ptrdiff_t len)
       FDDEBUG(fprintf(stderr,"Read on %d returned %ld\n",fd,rret));
       return rret;
 
+    case FD_PTY:
+      {
+	struct my_pty *pty = (struct my_pty *)handle;
+	handle = pty->read_pipe;
+
+	if (pty->conpty && !pty->other && !check_pty_clients(pty)) {
+	  /* Master side, no local slave and all known clients are dead.
+	   *
+	   * Terminate the ConPTY so that we can get an EOF.
+	   */
+	  Pike_NT_ClosePseudoConsole(pty->conpty);
+	  pty->conpty = 0;
+	}
+      }
+      /* FALLTHRU */
+
     case FD_PIPE:
       if (len) {
 	DWORD available_bytes = 0;
@@ -2125,64 +2238,67 @@ PMOD_EXPORT ptrdiff_t debug_fd_read(FD fd, void *to, ptrdiff_t len)
 	  }
 	}
       }
-      /* FALLTHRU */
+      break;
+
     case FD_CONSOLE:
     case FD_FILE:
-      ret=0;
-      while(len && !ReadFile(handle, to,
-			     DO_NOT_WARN((DWORD)len),
-			     &ret,0) && ret<=0) {
-	unsigned int err = GetLastError();
-	release_fd(fd);
-	set_errno_from_win32_error (err);
-	switch(err)
-	{
-	  case ERROR_NOT_ENOUGH_MEMORY:
-	    /* This can happen when reading from stdin, and can be due
-	     * to running out of OS io-buffers. cf ReadConsole():
-	     *
-	     * | lpBuffer [out]
-	     * |   A pointer to a buffer that receives the data read from
-	     * |   the console input buffer.
-	     * |
-	     * |   The storage for this buffer is allocated from a shared
-	     * |   heap for the process that is 64 KB in size. The maximum
-	     * |   size of the buffer will depend on heap usage.
-	     *
-	     * The limit seems to be 26608 bytes on Windows Server 2003,
-	     * and 31004 bytes on some versions of Windows XP.
-	     *
-	     * The gdb people ran into the same bug in 2006.
-	     * cf http://permalink.gmane.org/gmane.comp.gdb.patches/29669
-	     *
-	     * FIXME: We ought to attempt to fill the remainder of
-	     *        the buffer on success and full read, but as
-	     *        this failure mode usually only happens with
-	     *        the console, we would risk blocking, so let
-	     *        the higher levels of code deal with the
-	     *        resulting short reads.
-	     */
-	    /* Halve the size of the request and retry. */
-	    len = len >> 1;
-	    if (len) continue;
-	    /* Total failure. Fall out to the generic error return. */
-	    break;
-	  case ERROR_BROKEN_PIPE:
-	    /* Pretend we reached the end of the file */
-	    return 0;
-	}
-	FDDEBUG(fprintf(stderr,"Read failed %d\n",errno));
-	return -1;
-      }
-      FDDEBUG(fprintf(stderr,"Read on %d returned %ld\n",fd,ret));
-      release_fd(fd);
-      return ret;
+      break;
 
     default:
       errno=ENOTSUPP;
       release_fd(fd);
       return -1;
   }
+
+  ret=0;
+  while(len && !ReadFile(handle, to,
+			 DO_NOT_WARN((DWORD)len),
+			 &ret,0) && ret<=0) {
+    unsigned int err = GetLastError();
+    release_fd(fd);
+    set_errno_from_win32_error (err);
+    switch(err)
+    {
+      case ERROR_NOT_ENOUGH_MEMORY:
+	/* This can happen when reading from stdin, and can be due
+	 * to running out of OS io-buffers. cf ReadConsole():
+	 *
+	 * | lpBuffer [out]
+	 * |   A pointer to a buffer that receives the data read from
+	 * |   the console input buffer.
+	 * |
+	 * |   The storage for this buffer is allocated from a shared
+	 * |   heap for the process that is 64 KB in size. The maximum
+	 * |   size of the buffer will depend on heap usage.
+	 *
+	 * The limit seems to be 26608 bytes on Windows Server 2003,
+	 * and 31004 bytes on some versions of Windows XP.
+	 *
+	 * The gdb people ran into the same bug in 2006.
+	 * cf http://permalink.gmane.org/gmane.comp.gdb.patches/29669
+	 *
+	 * FIXME: We ought to attempt to fill the remainder of
+	 *        the buffer on success and full read, but as
+	 *        this failure mode usually only happens with
+	 *        the console, we would risk blocking, so let
+	 *        the higher levels of code deal with the
+	 *        resulting short reads.
+	 */
+	/* Halve the size of the request and retry. */
+	len = len >> 1;
+	if (len) continue;
+	/* Total failure. Fall out to the generic error return. */
+	break;
+      case ERROR_BROKEN_PIPE:
+	/* Pretend we reached the end of the file */
+        return 0;
+    }
+    FDDEBUG(fprintf(stderr,"Read failed %d\n",errno));
+    return -1;
+  }
+  FDDEBUG(fprintf(stderr,"Read on %d returned %ld\n",fd,ret));
+  release_fd(fd);
+  return ret;
 }
 
 PMOD_EXPORT PIKE_OFF_T debug_fd_lseek(FD fd, PIKE_OFF_T pos, int where)
@@ -2406,6 +2522,10 @@ PMOD_EXPORT int debug_fd_fstat(FD fd, PIKE_STAT_T *s)
       s->st_mode=S_IFSOCK;
       break;
 
+    case FD_PTY:
+      s->st_mode = S_IFIFO;
+      break;
+
     default:
       switch(GetFileType(h))
       {
@@ -2462,7 +2582,17 @@ static void dump_FDSET(FD_SET *x, int fds)
     fprintf(stderr,"[");
     for(e = 0; e < FD_SETSIZE; e++)
     {
-      if(FD_ISSET(da_handle[e],x))
+      HANDLE h = da_handle[e];
+      if (fd_type[e] == FD_PTY){
+	struct my_pty *pty = (struct my_pty *)h;
+	if(FD_ISSET(pty->read_pipe, x))
+	{
+	  h = pty->read_pipe;
+	} else {
+	  h = pty->write_pipe;
+	}
+      }
+      if(FD_ISSET(h, x))
       {
 	if(!first) fprintf(stderr,",",e);
 	fprintf(stderr,"%d",e);
@@ -2552,6 +2682,22 @@ PMOD_EXPORT FD debug_fd_dup(FD from)
 
   if (fd_to_handle(from, &type, &h, 0) < 0) return -1;
 
+  if (type == FD_PTY) {
+    struct my_pty *pty = (struct my_pty *)h;
+
+    fd = allocate_fd(type, h);
+
+    release_fd(from);
+
+    if (fd < 0) return -1;
+
+    add_ref(pty);
+    pty->fd_refs++;
+
+    release_fd(fd);
+    return fd;
+  }
+
   fd = allocate_fd(type,
 		   (type == FD_SOCKET)?
 		   (HANDLE)INVALID_SOCKET:INVALID_HANDLE_VALUE);
@@ -2590,8 +2736,18 @@ PMOD_EXPORT FD debug_fd_dup2(FD from, FD to)
 
   if (fd_to_handle(from, &type, &h, 0) < 0) return -1;
 
-  if(!DuplicateHandle(p, h, p, &x, 0, 0, DUPLICATE_SAME_ACCESS))
-  {
+  if (type == FD_PTY) {
+    struct my_pty *pty = (struct my_pty *)h;
+    /* Note that we need to hold a reference, so that we
+     * do not disappear when from is released below.
+     *
+     * The reference is then either stolen by reallocate_fd()
+     * or freed by close_pty().
+     */
+    add_ref(pty);
+    pty->fd_refs++;
+    x = h;
+  } else if(!DuplicateHandle(p, h, p, &x, 0, 0, DUPLICATE_SAME_ACCESS)) {
     release_fd(from);
     set_errno_from_win32_error (GetLastError());
     return -1;
@@ -2606,7 +2762,11 @@ PMOD_EXPORT FD debug_fd_dup2(FD from, FD to)
   if (reallocate_fd(to, type, x) < 0) {
     release_fd(to);
 
-    if (type == FD_SOCKET) {
+    if (type == FD_PTY) {
+      mt_lock(&fd_mutex);
+      close_pty((struct my_pty *)x);
+      mt_unlock(&fd_mutex);
+    } else if (type == FD_SOCKET) {
       closesocket((SOCKET)x);
     } else {
       CloseHandle(x);
@@ -2681,7 +2841,103 @@ PMOD_EXPORT const char *debug_fd_inet_ntop(int af, const void *addr,
   }
   return inet_ntop_funp(af, addr, cp, sz);
 }
-#endif /* HAVE_WINSOCK_H && !__GNUC__ */
+
+PMOD_EXPORT int debug_fd_openpty(int *master, int *slave,
+				 char *ignored_name,
+				 void *ignored_term,
+				 struct winsize *winp)
+{
+  struct my_pty *master_pty = NULL;
+  struct my_pty *slave_pty = NULL;
+  int master_fd = -1;
+  int slave_fd = -1;
+  COORD sz;
+
+  if (!Pike_NT_CreatePseudoConsole) {
+    errno = ENOTSUPP;
+    return -1;
+  }
+
+  if (!(master_pty = calloc(sizeof(struct my_pty), 1))) {
+    errno = ENOMEM;
+    goto fail;
+  }
+  if (!(slave_pty = calloc(sizeof(struct my_pty), 1))) {
+    errno = ENOMEM;
+    goto fail;
+  }
+
+  add_ref(master_pty);
+  master_pty->fd_refs++;
+  master_pty->read_pipe = INVALID_HANDLE_VALUE;
+  master_pty->write_pipe = INVALID_HANDLE_VALUE;
+  master_pty->other = slave_pty;
+  add_ref(slave_pty);
+  slave_pty->fd_refs++;
+  slave_pty->read_pipe = INVALID_HANDLE_VALUE;
+  slave_pty->write_pipe = INVALID_HANDLE_VALUE;
+  slave_pty->other = master_pty;
+
+  master_fd = allocate_fd(FD_PTY, (HANDLE)master_pty);
+  if (master_fd < 0) goto fail;
+
+  slave_fd = allocate_fd(FD_PTY, (HANDLE)slave_pty);
+  if (slave_fd < 0) goto fail;
+
+  if (!CreatePipe(&slave_pty->read_pipe, &master_pty->write_pipe, NULL, 0)) {
+    goto win32_fail;
+  }
+  if (!CreatePipe(&master_pty->read_pipe, &slave_pty->write_pipe, NULL, 0)) {
+    goto win32_fail;
+  }
+
+  /* Some reasonable defaults. */
+  sz.X = 80;
+  sz.Y = 25;
+
+  if (winp) {
+    sz.X = winp->ws_col;
+    sz.Y = winp->ws_row;
+  }
+
+  if (FAILED(Pike_NT_CreatePseudoConsole(sz, slave_pty->read_pipe,
+					 slave_pty->write_pipe,
+					 0, &master_pty->conpty))) {
+    goto win32_fail;
+  }
+
+  release_fd(master_fd);
+  release_fd(slave_fd);
+
+  *master = master_fd;
+  *slave = slave_fd;
+
+  return 0;
+
+ win32_fail:
+    set_errno_from_win32_error(GetLastError());
+
+ fail:
+  /* NB: Order significant!
+   *
+   * In the case where master_fd >= 0 and slave_fd < 0, the
+   * slave_pty must not have been freed when master_fd is closed.
+   */
+  if (master_fd >= 0) {
+    release_fd(master_fd);
+    fd_close(master_fd);
+  } else if (master_pty) {
+    free(master_pty);
+  }
+  if (slave_fd >= 0) {
+    release_fd(slave_fd);
+    fd_close(slave_fd);
+  } else if (slave_pty) {
+    free(slave_pty);
+  }
+
+  return -1;
+}
 
 #ifdef EMULATE_DIRECT
 PMOD_EXPORT DIR *opendir(char *dir)
@@ -2761,13 +3017,15 @@ PMOD_EXPORT void closedir(DIR *dir)
   }
   free(dir);
 }
-#endif
+#endif /* EMULATE_DIRECT */
 
-#if defined(HAVE_WINSOCK_H) && defined(USE_DL_MALLOC)
+#ifdef USE_DL_MALLOC
 /* NB: We use some calls above that allocate memory with the libc malloc. */
 #undef free
 static inline void libc_free(void *ptr)
 {
   if (ptr) free(ptr);
 }
-#endif
+#endif /* USE_DL_MALLOC */
+
+#endif /* HAVE_WINSOCK_H */
diff --git a/src/fdlib.h b/src/fdlib.h
index 39a1a2901706b522dc7c4d51e42fd3d6cb6d4d26..b632ff734f1f757e776474f7874941eac2b54bd1 100644
--- a/src/fdlib.h
+++ b/src/fdlib.h
@@ -109,6 +109,13 @@ typedef struct _STARTUPINFOEXW
 } STARTUPINFOEXW, *LPSTARTUPINFOEXW;
 #endif
 
+#ifndef HAVE_STRUCT_WINSIZE
+/* Typically found in <termios.h>. */
+struct winsize {
+  unsigned short ws_row, ws_col, ws_xpixel, ws_ypixel;
+};
+#endif
+
 #define SOCKFUN1(NAME,T1) PMOD_EXPORT int PIKE_CONCAT(debug_fd_,NAME) (FD,T1);
 #define SOCKFUN2(NAME,T1,T2) PMOD_EXPORT int PIKE_CONCAT(debug_fd_,NAME) (FD,T1,T2);
 #define SOCKFUN3(NAME,T1,T2,T3) PMOD_EXPORT int PIKE_CONCAT(debug_fd_,NAME) (FD,T1,T2,T3);
@@ -159,10 +166,12 @@ typedef struct _STARTUPINFOEXW
 #define fd_dup2(fd,to) dmalloc_register_fd(debug_fd_dup2(dmalloc_touch_fd(fd),dmalloc_close_fd(to)))
 #define fd_connect(fd,X,Z) debug_fd_connect(dmalloc_touch_fd(fd),(X),(Z))
 #define fd_inet_ntop(af,addr,cp,sz) debug_fd_inet_ntop(af,addr,cp,sz)
+#define fd_openpty(m,s,name,term,win) debug_fd_openpty(m,s,name,term,win)
 
 
 /* Prototypes begin here */
 PMOD_EXPORT void set_errno_from_win32_error (unsigned long err);
+void free_pty(struct my_pty *pty);
 int fd_to_handle(int fd, int *type, HANDLE *handle, int exclusive);
 void release_fd(int fd);
 PMOD_EXPORT char *debug_fd_info(int fd);
@@ -210,6 +219,10 @@ PMOD_EXPORT FD debug_fd_dup(FD from);
 PMOD_EXPORT FD debug_fd_dup2(FD from, FD to);
 PMOD_EXPORT const char *debug_fd_inet_ntop(int af, const void *addr,
 					   char *cp, size_t sz);
+PMOD_EXPORT int debug_fd_openpty(int *master, int *slave,
+				 char *ignored_name,
+				 void *ignored_term,
+				 struct winsize *winp);
 /* Prototypes end here */
 
 #undef SOCKFUN1
@@ -245,6 +258,7 @@ PMOD_EXPORT const char *debug_fd_inet_ntop(int af, const void *addr,
 #define fd_shutdown_write SD_SEND
 #define fd_shutdown_both SD_BOTH
 
+#define FD_PTY -6
 #define FD_PIPE -5
 #define FD_SOCKET -4
 #define FD_CONSOLE -3
@@ -262,6 +276,17 @@ struct my_fd_set_s
   char bits[FD_SETSIZE/8];
 };
 
+struct my_pty
+{
+  INT32 refs;		/* Total number of references. */
+  INT32 fd_refs;	/* Number of references from da_handle[]. */
+  HPCON conpty;		/* Only valid for master side. */
+  struct my_pty *other;	/* Other end (if any), NULL otherwise. */
+  struct pid_status *clients; /* List of client processes. */
+  HANDLE read_pipe;	/* Pipe that supports read(). */
+  HANDLE write_pipe;	/* Pipe that supports write(). */
+};
+
 typedef struct my_fd_set_s my_fd_set;
 
 #ifdef PIKE_DEBUG
@@ -312,6 +337,17 @@ extern int fd_type[FD_SETSIZE];
 #define S_IFIFO 0010000
 #endif
 
+/* Make dynamically loaded functions available to the rest of pike. */
+#undef NTLIB
+#define NTLIB(LIB)
+
+#undef NTLIBFUNC
+#define NTLIBFUNC(LIB, RET, NAME, ARGLIST)				\
+  typedef RET (WINAPI * PIKE_CONCAT3(Pike_NT_, NAME, _type)) ARGLIST;	\
+  extern PIKE_CONCAT3(Pike_NT_, NAME, _type) PIKE_CONCAT(Pike_NT_, NAME)
+
+#include "ntlibfuncs.h"
+
 
 /* This may be inaccurate! /Hubbe */
 #if defined(__NT__) && !defined(__MINGW32__)
@@ -580,6 +616,10 @@ typedef struct my_fd_set_s my_fd_set;
 #define SOCKET_CAPABILITIES (fd_INTERPROCESSABLE | fd_BIDIRECTIONAL | fd_CAN_NONBLOCK | fd_CAN_SHUTDOWN)
 #define TTY_CAPABILITIES (fd_TTY | fd_INTERPROCESSABLE | fd_BIDIRECTIONAL | fd_CAN_NONBLOCK)
 
+#ifdef HAVE_OPENPTY
+#define fd_openpty	openpty	/* FIXME */
+#endif
+
 #endif /* Don't HAVE_WINSOCK */
 
 #ifndef SEEK_SET
diff --git a/src/modules/_Stdio/file.c b/src/modules/_Stdio/file.c
index 551ff6bfb940452f99c5bc1ce988cb5d706b61c8..773adc1c9cf612521eba60451ecb99e8a6565dbd 100644
--- a/src/modules/_Stdio/file.c
+++ b/src/modules/_Stdio/file.c
@@ -2579,8 +2579,12 @@ static int my_grantpt(int m)
 #define unlockpt(m)	0
 #endif
 
-#if !defined(HAVE_OPENPTY) && defined(HAVE_PTSNAME) && defined(HAVE_POSIX_OPENPT)
-static int my_openpty(int *master, int *slave, void *ignored_name,
+#ifdef fd_openpty
+#ifndef HAVE_OPENPTY
+#define HAVE_OPENPTY
+#endif
+#elif defined(HAVE_POSIX_OPENPT)
+static int fd_openpty(int *master, int *slave, void *ignored_name,
 		      void *ignored_termp, void *ignored_winp)
 {
   int m;
@@ -2614,7 +2618,6 @@ static int my_openpty(int *master, int *slave, void *ignored_name,
   return -1;
 }
 #define HAVE_OPENPTY
-#define openpty(M, S, N, T, W)	my_openpty(M, S, N, T, W)
 #endif
 
 /*! @decl string grantpt()
@@ -4624,7 +4627,7 @@ static void file_pipe(INT32 args)
 #ifdef HAVE_OPENPTY
     if (!(type & ~(TTY_CAPABILITIES)))
     {
-      i = openpty(inout, inout + 1, NULL, NULL, NULL);
+      i = fd_openpty(inout, inout + 1, NULL, NULL, NULL);
       if (i >= 0) {
 	type = TTY_CAPABILITIES;
 	break;
diff --git a/src/signal_handler.c b/src/signal_handler.c
index 22db1e1356a57e5f0a1c8e39c94e71152d63e2c6..808a392584197008105f5ed573217e31f545f0ee 100644
--- a/src/signal_handler.c
+++ b/src/signal_handler.c
@@ -1261,6 +1261,8 @@ struct pid_status
   INT_TYPE pid;
 #ifdef __NT__
   HANDLE handle;
+  struct pid_status *next_pty_client;	/* PTY client chain. */
+  struct my_pty *pty;			/* PTY master, refcounted. */
 #else
   INT_TYPE sig;
   INT_TYPE flags;
@@ -1286,6 +1288,48 @@ static void init_pid_status(struct object *UNUSED(o))
 #endif
 }
 
+#ifdef __NT__
+struct pid_status *pid_status_unlink_pty(struct pid_status *pid)
+{
+  struct pid_status *ret;
+  if (!pid) return NULL;
+  ret = pid->next_pty_client;
+  pid->next_pty_client = NULL;
+  if (pid->pty) {
+    free_pty(pid->pty);
+    pid->pty = NULL;
+  }
+  return ret;
+}
+
+static void pid_status_link_pty(struct pid_status *pid, struct my_pty *pty)
+{
+  add_ref(pty);
+  pid->pty = pty;
+  pid->next_pty_client = pty->clients;
+  pty->clients = pid;
+}
+
+int check_pty_clients(struct my_pty *pty)
+{
+  struct pid_status *pid;
+  while ((pid = pty->clients)) {
+    DWORD status;
+    /* FIXME: RACE: pid->handle my be INVALID_HANDLE_VALUE if
+     *              the process is about to be started.
+     */
+    if ((pid->pid == -1) && (pid->handle == INVALID_HANDLE_VALUE)) return 1;
+    if (GetExitCodeProcess(pid->handle, &status) &&
+	(status == STILL_ACTIVE)) {
+      return 1;
+    }
+    /* Process has terminated. Unlink and check the next. */
+    pty->clients = pid_status_unlink_pty(pid);
+  }
+  return 0;
+}
+#endif /* __NT__ */
+
 static void exit_pid_status(struct object *UNUSED(o))
 {
 #ifdef USE_PID_MAPPING
@@ -1298,6 +1342,25 @@ static void exit_pid_status(struct object *UNUSED(o))
 #endif
 
 #ifdef __NT__
+  if (THIS->pty) {
+    struct my_pty *pty = THIS->pty;
+    struct pid_status **pidptr = &pty->clients;
+    while (*pidptr && (*pidptr != THIS)) {
+      pidptr = &(*pidptr)->next_pty_client;
+    }
+    if (*pidptr) {
+      *pidptr = pid_status_unlink_pty(THIS);
+    }
+    if (!pty->clients && !pty->other && pty->conpty) {
+      /* Last client for this ConPTY has terminated,
+       * and our local copy of the slave has been closed.
+       *
+       * Close the ConPTY.
+       */
+      Pike_NT_ClosePseudoConsole(pty->conpty);
+      pty->conpty = 0;
+    }
+  }
   CloseHandle(THIS->handle);
 #endif
 }
@@ -2140,7 +2203,8 @@ static void free_perishables(struct perishables *storage)
  */
 static HANDLE get_inheritable_handle(struct mapping *optional,
 				     char *name,
-				     int for_reading)
+				     int for_reading,
+				     HPCON *conpty)
 {
   HANDLE ret = DO_NOT_WARN(INVALID_HANDLE_VALUE);
   struct svalue *save_stack=Pike_sp;
@@ -2151,6 +2215,7 @@ static HANDLE get_inheritable_handle(struct mapping *optional,
     {
       INT32 fd=fd_from_object(tmp->u.object);
       HANDLE h;
+      int type;
 
       if(fd == -1)
 	Pike_error("File for %s is not open.\n",name);
@@ -2163,9 +2228,59 @@ static HANDLE get_inheritable_handle(struct mapping *optional,
 	fd=fd_from_object(Pike_sp[-1].u.object);
       }
 
-      if(fd_to_handle(fd, NULL, &h, 0) < 0)
+      if(fd_to_handle(fd, &type, &h, 0) < 0)
 	Pike_error("File for %s is not open.\n",name);
 
+      if (type == FD_PTY) {
+	struct my_pty *pty = (struct my_pty *)h;
+	if (!conpty || pty->conpty || !pty->other || !pty->other->conpty ||
+	    (*conpty && (*conpty != pty->other->conpty))) {
+	  /* Master side, or detached slave,
+	   * or we have already selected a different conpty.
+	   */
+	  if (for_reading) {
+	    h = pty->read_pipe;
+	  } else {
+	    h = pty->write_pipe;
+	  }
+	} else {
+	  /* Slave side. Get the conpty from the master side.
+	   *
+	   * NB: Stdin, stdout and stderr are handled automatically
+	   *     by setting the conpty. The conpty has duplicated
+	   *     handles of the original pipes, and in turn provides
+	   *     actual CONSOLE handles to the subprocess. We thus
+	   *     do NOT return the base pipe handle.
+	   *
+	   * BUGS: It is not possible to have multiple conpty
+	   *       objects for the same process, so the first
+	   *       pty for stdin, stdout and stderr wins
+	   *       (see above).
+	   *
+	   * BUGS: As the conpty is a separate process that survives
+	   *       or subprocess terminating, we need to keep track
+	   *       of which master pty the process was bound to so
+	   *       that we can close the corresponding conpty
+	   *       when no one else will use it.
+	   */
+	  pid_status_link_pty(THIS, pty->other);
+	  *conpty = pty->other->conpty;
+	  release_fd(fd);
+
+	  /* From the documentation for GetStdHandle():
+	   *
+	   *   If the existing value of the standard handle is NULL, or
+	   *   the existing value of the standard handle looks like a
+	   *   console pseudohandle, the handle is replaced with a
+	   *   console handle.
+	   *
+	   * This is not documented in conjunction with CreateProcess() or
+	   * PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, but it seems to work.
+	   */
+	  return 0;
+	}
+      }
+
       if(!DuplicateHandle(GetCurrentProcess(),	/* Source process */
 			  h,
 			  GetCurrentProcess(),	/* Target process */
@@ -2847,9 +2962,9 @@ void f_create_process(INT32 args)
     HANDLE t1 = DO_NOT_WARN(INVALID_HANDLE_VALUE);
     HANDLE t2 = DO_NOT_WARN(INVALID_HANDLE_VALUE);
     HANDLE t3 = DO_NOT_WARN(INVALID_HANDLE_VALUE);
-    STARTUPINFOW info;
+    STARTUPINFOEXW info;
     PROCESS_INFORMATION proc;
-    int ret,err;
+    int ret = 0, err;
     WCHAR *filename=NULL, *command_line=NULL, *dir=NULL;
     WCHAR *env=NULL;
 
@@ -2951,23 +3066,36 @@ void f_create_process(INT32 args)
      */
 
     memset(&info,0,sizeof(info));
-    info.cb=sizeof(info);
+    info.StartupInfo.cb = sizeof(info);
 
-    GetStartupInfoW(&info);
+#if 0
+    /* CAUTION!!!!
+     *
+     * This function fills in several reserved fields in the
+     * StartupInfo, which in turn cause CreateProcessW() below
+     * to fail with error ERROR_INVALID_PARAMETER (87) when
+     * EXTENDED_STARTUPINFO_PRESENT is set.
+     */
+    GetStartupInfoW(&info.StartupInfo);
+#endif
 
     /* Protect inherit status for handles */
     LOCK_IMUTEX(&handle_protection_mutex);
 
-    info.dwFlags|=STARTF_USESTDHANDLES;
-    info.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
-    info.hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
-    info.hStdError=GetStdHandle(STD_ERROR_HANDLE);
-    SetHandleInformation(info.hStdInput, HANDLE_FLAG_INHERIT, 0);
-    SetHandleInformation(info.hStdOutput, HANDLE_FLAG_INHERIT, 0);
-    SetHandleInformation(info.hStdError, HANDLE_FLAG_INHERIT, 0);
+    info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+    info.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+    info.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+    info.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+    SetHandleInformation(info.StartupInfo.hStdInput, HANDLE_FLAG_INHERIT, 0);
+    SetHandleInformation(info.StartupInfo.hStdOutput, HANDLE_FLAG_INHERIT, 0);
+    SetHandleInformation(info.StartupInfo.hStdError, HANDLE_FLAG_INHERIT, 0);
+
+    info.lpAttributeList = NULL;
 
     if(optional)
     {
+      HPCON conpty = (HPCON)0;
+
       if( (tmp=simple_mapping_string_lookup(optional, "cwd")) )
       {
 	if(TYPEOF(*tmp) == T_STRING)
@@ -2977,14 +3105,52 @@ void f_create_process(INT32 args)
 	}
       }
 
-      t1=get_inheritable_handle(optional, "stdin",1);
-      if(t1 != DO_NOT_WARN(INVALID_HANDLE_VALUE)) info.hStdInput=t1;
+      t1 = get_inheritable_handle(optional, "stdin", 1, &conpty);
+      if(t1 != DO_NOT_WARN(INVALID_HANDLE_VALUE)) {
+	info.StartupInfo.hStdInput = t1;
+      }
 
-      t2=get_inheritable_handle(optional, "stdout",0);
-      if(t2 != DO_NOT_WARN(INVALID_HANDLE_VALUE)) info.hStdOutput=t2;
+      t2 = get_inheritable_handle(optional, "stdout", 0, &conpty);
+      if(t2 != DO_NOT_WARN(INVALID_HANDLE_VALUE)) {
+	info.StartupInfo.hStdOutput = t2;
+      }
 
-      t3=get_inheritable_handle(optional, "stderr",0);
-      if(t3 != DO_NOT_WARN(INVALID_HANDLE_VALUE)) info.hStdError=t3;
+      t3 = get_inheritable_handle(optional, "stderr", 0, &conpty);
+      if(t3 != DO_NOT_WARN(INVALID_HANDLE_VALUE)) {
+	info.StartupInfo.hStdError = t3;
+      }
+
+      if (conpty) {
+	LPPROC_THREAD_ATTRIBUTE_LIST attrlist = NULL;
+	SIZE_T attrlist_sz = 0;
+	/* Hook in the pty controller. */
+
+	/* Get the required size for a single attribute. */
+	Pike_NT_InitializeProcThreadAttributeList(attrlist, 1, 0, &attrlist_sz);
+
+	if (!(attrlist = malloc(attrlist_sz)) ||
+	    !(Pike_NT_InitializeProcThreadAttributeList(attrlist, 1, 0,
+							&attrlist_sz) &&
+	      (info.lpAttributeList = attrlist)) ||
+	    !Pike_NT_UpdateProcThreadAttribute(attrlist, 0,
+					       PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
+					       conpty, sizeof(conpty),
+					       NULL, NULL)) {
+	  /* Out of memory or similar. */
+	  if (attrlist) {
+	    err = GetLastError();
+	    if (!info.lpAttributeList) {
+	      /* InitializeProcThreadAttributeList() failed. */
+	      free(attrlist);
+	    }
+	  } else {
+	    /* malloc() failed. */
+	    err = ERROR_NOT_ENOUGH_MEMORY;
+	  }
+
+	  goto fail;
+	}
+      }
 
 	if((tmp=simple_mapping_string_lookup(optional, "env")))
 	{
@@ -3037,28 +3203,35 @@ void f_create_process(INT32 args)
     THREADS_ALLOW_UID();
 
 
-    SetHandleInformation(info.hStdInput, HANDLE_FLAG_INHERIT,
+    SetHandleInformation(info.StartupInfo.hStdInput, HANDLE_FLAG_INHERIT,
 			 HANDLE_FLAG_INHERIT);
-    SetHandleInformation(info.hStdOutput, HANDLE_FLAG_INHERIT,
+    SetHandleInformation(info.StartupInfo.hStdOutput, HANDLE_FLAG_INHERIT,
 			 HANDLE_FLAG_INHERIT);
-    SetHandleInformation(info.hStdError, HANDLE_FLAG_INHERIT,
+    SetHandleInformation(info.StartupInfo.hStdError, HANDLE_FLAG_INHERIT,
 			 HANDLE_FLAG_INHERIT);
     ret = CreateProcessW(filename,
 			 command_line,
 			 NULL,  /* process security attribute */
 			 NULL,  /* thread security attribute */
 			 1,     /* inherithandles */
-			 CREATE_UNICODE_ENVIRONMENT,     /* create flags */
+			 CREATE_UNICODE_ENVIRONMENT |
+			 EXTENDED_STARTUPINFO_PRESENT,     /* create flags */
 			 env,  /* environment */
 			 dir,   /* current dir */
-			 &info,
+			 &info.StartupInfo,
 			 &proc);
     err=GetLastError();
     
     THREADS_DISALLOW_UID();
 
+  fail:
+
     UNLOCK_IMUTEX(&handle_protection_mutex);
 
+    if (info.lpAttributeList) {
+      Pike_NT_DeleteProcThreadAttributeList(info.lpAttributeList);
+      free(info.lpAttributeList);
+    }
     if(env) free(env);
     if(dir) free(dir);
     if(filename) free(filename);
diff --git a/src/signal_handler.h b/src/signal_handler.h
index 106bf2040ec9b385a02d96742d56e520417e461c..dce153ba17f5ec7189ea7fc916a6cf7b3e19e627 100644
--- a/src/signal_handler.h
+++ b/src/signal_handler.h
@@ -14,6 +14,10 @@ struct sigdesc;
 void my_signal(int sig, sigfunctype fun);
 PMOD_EXPORT void check_signals(struct callback *foo, void *bar, void *gazonk);
 void set_default_signal_handler(int signum, void (*func)(INT32));
+#ifdef __NT__
+struct pid_status *pid_status_unlink_pty(struct pid_status *pid);
+int check_pty_clients(struct my_pty *pty);
+#endif
 void process_started(pid_t pid);
 void process_done(pid_t pid, const char *from);
 struct wait_data;