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;