From 32eb5d2acd542855a34fe198594e00c496308009 Mon Sep 17 00:00:00 2001 From: Hugo Melder Date: Tue, 23 Aug 2022 18:43:14 +0200 Subject: [PATCH] win32: Support overlapped (asynchronous) I/O on standard streams in GSFileHandle * win32: Support overlapped I/O on standard streams in GSFileHandle * Add isStandardInput instance variable * Restrict PeekConsoleInput on stdin * Update ChangeLog --- ChangeLog | 9 +++ Source/GSFileHandle.h | 3 + Source/win32/GSFileHandle.m | 111 +++++++++++++++++++++++------- Tests/base/NSRunLoop/performers.m | 2 - 4 files changed, 100 insertions(+), 25 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1d7df1c74..aad4d8541 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2022-08-16 Hugo Melder + + * Source/win32/GSFileHandle.m: + Support overlapped I/O on standard streams in GSFileHandle. + * Source/GSFileHandle.h: + Add the isStandardStream instance variable. + * Tests/base/NSRunLoop/performers.m: + Remove extraneous unistd header from unit test. + 2022-08-16 Richard Frith-Macdonald * Source/NSOperation.m: Remove restriction (of 8) on the maximum diff --git a/Source/GSFileHandle.h b/Source/GSFileHandle.h index 27fc485a2..0aa36d126 100644 --- a/Source/GSFileHandle.h +++ b/Source/GSFileHandle.h @@ -52,6 +52,9 @@ struct sockaddr_in; int descriptor; BOOL closeOnDealloc; BOOL isStandardFile; + // stdin, stdout, and stderr + BOOL isStandardStream; + BOOL isStandardInput; BOOL isNullDevice; BOOL isSocket; BOOL isNonBlocking; diff --git a/Source/win32/GSFileHandle.m b/Source/win32/GSFileHandle.m index a11533873..aa3af6e77 100644 --- a/Source/win32/GSFileHandle.m +++ b/Source/win32/GSFileHandle.m @@ -1065,6 +1065,7 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; if (self) { readOK = NO; + isStandardStream = YES; } } return self; @@ -1083,6 +1084,8 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; if (self) { writeOK = NO; + isStandardStream = YES; + isStandardInput = YES; } } return self; @@ -1101,6 +1104,7 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; if (self) { readOK = NO; + isStandardStream = YES; } } return self; @@ -1158,8 +1162,7 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; { if (GetFileType(h) == FILE_TYPE_PIPE) { - /* If we can't get named pipe info, we assume this is a socket. - */ + // If we can't get named pipe info, we assume this is a socket. if (GetNamedPipeInfo(h, 0, 0, 0, 0) == 0) { isSocket = YES; @@ -2089,33 +2092,87 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; } else { + HANDLE h; + h = (HANDLE)_get_osfhandle(descriptor); + + /* Overlapped (asynchronous) I/O on a standard stream requires + * a different interface to that of a pipe. + * + * Opening a standard stream ("CONIN$", "CONOUT$", "CONERR$") via + * CreateFile() with the FILE_FLAG_OVERLAPPED flag has no effect + * on the handle; the parameter dwFlagsAndAttributes is ignored when + * creating a standard stream handle. + * + * A Windows standard stream is not an anonymous or named pipe and + * PeekNamedPipe is therefore not supported. Instead, PeekConsoleInput + * is used to "peek" into the standard stream. + */ + if (YES == isStandardInput && YES == isStandardStream) + { + /* Stores the number of input records read + */ + DWORD bytes = 0; + + /* PeekConsoleInput fails, if it returns a non-zero value. + */ + if (PeekConsoleInput(h, 0, 0, &bytes) == 0) + { + DWORD e = GetLastError(); + NSString *s; + + s = [NSString stringWithFormat: @"Standard input peek problem: %lu - %@", e, + [NSError _last]]; + [readInfo setObject: s forKey: GSFileHandleNotificationError]; + + NSLog(@"%@", s); + return; + } + else if (bytes == 0) + { + return; // No data available yet. + } + } + else if (NO == isStandardInput && YES == isStandardStream) { + NSString *s; + + s = @"Reading from stdout and stderr is not available."; + [readInfo setObject: s forKey: GSFileHandleNotificationError]; + + NSLog(@"%@", s); + return; + } /* If this is not a socket or a standard file, we assume it's a pipe * and therefore we need to check to see if data really is available. */ - if (NO == isSocket && NO == isStandardFile) - { - HANDLE h = (HANDLE)_get_osfhandle(descriptor); - DWORD bytes = 0; + else if (NO == isSocket && NO == isStandardFile) + { + DWORD bytes = 0; - if (PeekNamedPipe(h, 0, 0, 0, &bytes, 0) == 0) - { - DWORD e = GetLastError(); + if (PeekNamedPipe(h, 0, 0, 0, &bytes, 0) == 0) + { + DWORD e = GetLastError(); if (e != ERROR_BROKEN_PIPE && e != ERROR_HANDLE_EOF) - { - NSLog(@"pipe peek problem %lu: %@", e, [NSError _last]); - return; - } - /* In the case of a broken pipe, we fall through so that a read - * attempt is performed allowing higer level code to notice the - * problem and deal with it. - */ - } - else if (bytes == 0) - { - return; // No data available yet. - } - } + { + NSString *s; + + s = [NSString stringWithFormat: @"pipe peek problem: %lu - %@", e, + [NSError _last]]; + [readInfo setObject: s forKey: GSFileHandleNotificationError]; + + NSLog(@"%@", s); + return; + } + /* In the case of a broken pipe, we fall through so that a read + * attempt is performed allowing higer level code to notice the + * problem and deal with it. + */ + } + else if (bytes == 0) + { + return; // No data available yet. + } + } if (operation == NSFileHandleDataAvailableNotification) { @@ -2417,6 +2474,14 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; { return; } + /* Invoking SetNamedPipeHandleState on a standard stream results in an + * ERROR_INVALID_FUNCTION (1) error message. Proceed only if the + * file descriptor is not a standard stream. + */ + else if (isStandardStream == YES) + { + return; + } else if (isNonBlocking == flag) { return; diff --git a/Tests/base/NSRunLoop/performers.m b/Tests/base/NSRunLoop/performers.m index d7b7f2205..13f2ea883 100644 --- a/Tests/base/NSRunLoop/performers.m +++ b/Tests/base/NSRunLoop/performers.m @@ -7,8 +7,6 @@ #import #import -#include - int main() { START_SET("NSRunLoop performers")