summaryrefslogtreecommitdiff
path: root/libraries/base/cbits/IOutils.c
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/base/cbits/IOutils.c')
-rw-r--r--libraries/base/cbits/IOutils.c470
1 files changed, 470 insertions, 0 deletions
diff --git a/libraries/base/cbits/IOutils.c b/libraries/base/cbits/IOutils.c
new file mode 100644
index 0000000000..5b154e6616
--- /dev/null
+++ b/libraries/base/cbits/IOutils.c
@@ -0,0 +1,470 @@
+/*
+ * (c) The GHC Team 2017-2018.
+ *
+ * I/O Utility functions for Windows.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <winsock2.h>
+#include <windows.h>
+#include <io.h>
+#include <math.h>
+
+/* Import some functions defined in base. */
+extern void maperrno(void);
+
+/* Enum of Handle type. */
+typedef
+enum HandleType
+ {
+ TYPE_CHAR, // 0
+ TYPE_DISK, // 1
+ TYPE_PIPE, // 2
+ TYPE_SOCKET, // 3
+ TYPE_REMOTE, // 4
+ TYPE_RAW, // 5
+ TYPE_UNKNOWN // 6
+ } HANDLE_TYPE;
+
+/*
+ * handleReady(hwnd) checks to see whether input is available on the file
+ * handle 'hwnd'. Input meaning 'can I safely read at least a
+ * *character* from this file object without blocking?'
+ */
+int
+__handle_ready(HANDLE hFile, bool write, int msecs)
+{
+ DWORD handleType = GetFileType (hFile);
+
+ DWORD rc;
+ DWORD avail;
+
+ switch (handleType)
+ {
+ case FILE_TYPE_CHAR:
+ {
+ INPUT_RECORD buf[1];
+ DWORD count;
+
+ /* A Console Handle will appear to be ready
+ (WaitForSingleObject() returned WAIT_OBJECT_0) when
+ it has events in its input buffer, but these events might
+ not be keyboard events, so when we read from the Handle the
+ read() will block. So here we try to discard non-keyboard
+ events from a console handle's input buffer and then try
+ the WaitForSingleObject() again.
+ Phyx: I'm worried that we're discarding events someone else may need. */
+ while (true) // keep trying until we find a real key event
+ {
+ rc = WaitForSingleObject( hFile, msecs );
+ switch (rc)
+ {
+ case WAIT_TIMEOUT:
+ return false;
+ case WAIT_OBJECT_0:
+ break;
+ default:
+ /* WAIT_FAILED */
+ maperrno();
+ return -1;
+ }
+
+ while (true) // discard non-key events
+ {
+ /* I wonder if we can do better by grabbing a list of
+ input records at a time by using PeekConsoleInput. */
+ rc = PeekConsoleInput(hFile, buf, 1, &count);
+ if (rc == 0) {
+ rc = GetLastError();
+ if (rc == ERROR_INVALID_HANDLE || rc == ERROR_INVALID_FUNCTION)
+ return true;
+ else {
+ maperrno();
+ return -1;
+ }
+ }
+
+ if (count == 0)
+ break; /* no more events => wait again. */
+
+ /* discard console events that are not "key down", because
+ these will also be discarded by ReadFile(). */
+ if (buf[0].EventType == KEY_EVENT &&
+ buf[0].Event.KeyEvent.bKeyDown &&
+ buf[0].Event.KeyEvent.uChar.AsciiChar != '\0')
+ return true; /* it's a proper keypress. */
+ else
+ {
+ /* it's a non-key event, a key up event, or a
+ non-character key (e.g. shift). discard it. */
+ rc = ReadConsoleInput(hFile, buf, 1, &count);
+ if (rc == 0) {
+ rc = GetLastError();
+ if (rc == ERROR_INVALID_HANDLE || rc == ERROR_INVALID_FUNCTION)
+ return true;
+ else {
+ maperrno();
+ return -1;
+ }
+ }
+ }
+ }
+ }
+ }
+ case FILE_TYPE_DISK:
+ /* assume that disk files are always ready. */
+ return true;
+
+ case FILE_TYPE_PIPE:
+ {
+ // Try to see if this is a socket
+ //-------------------------
+ // Create new event
+ WSAEVENT newEvent = WSACreateEvent();
+
+ //-------------------------
+ // Associate event types FD_WRITE or FD_READ
+ // with the listening socket and NewEvent
+ rc = WSAEventSelect((SOCKET)hFile, newEvent, write ? FD_WRITE : FD_READ);
+
+ if (rc == WSAENOTSOCK)
+ {
+ CloseHandle (newEvent);
+
+ // WaitForMultipleObjects() doesn't work for pipes (it
+ // always returns WAIT_OBJECT_0 even when no data is
+ // available). If the HANDLE is a pipe, therefore, we try
+ // PeekNamedPipe:
+ //
+ rc = PeekNamedPipe( hFile, NULL, 0, NULL, &avail, NULL );
+ if (rc != 0)
+ return avail != 0;
+ else {
+ rc = GetLastError();
+ if (rc == ERROR_BROKEN_PIPE)
+ return true; // this is probably what we want
+
+ if (rc != ERROR_INVALID_HANDLE && rc != ERROR_INVALID_FUNCTION) {
+ maperrno();
+ return -1;
+ }
+ }
+ /* PeekNamedPipe didn't work - fall through to the general case */
+ }
+ else if (rc != 0)
+ {
+ CloseHandle (newEvent);
+ // It seems to be a socket but can't determine the state.
+ // Maybe not initialized. Either way, we know enough.
+ return false;
+ }
+
+ // Wait for the socket event to trigger.
+ rc = WaitForSingleObject( newEvent, msecs );
+ CloseHandle (newEvent);
+
+ /* 1 => Input ready, 0 => not ready, -1 => error */
+ switch (rc)
+ {
+ case WAIT_TIMEOUT:
+ return false;
+ case WAIT_OBJECT_0:
+ return true;
+ default:
+ {
+ /* WAIT_FAILED */
+ maperrno();
+ return -1;
+ }
+ }
+ }
+ default:
+ rc = WaitForSingleObject( hFile, msecs );
+
+ /* 1 => Input ready, 0 => not ready, -1 => error */
+ switch (rc)
+ {
+ case WAIT_TIMEOUT:
+ return false;
+ case WAIT_OBJECT_0:
+ return true;
+ default:
+ {
+ /* WAIT_FAILED */
+ maperrno();
+ return -1;
+ }
+ }
+ }
+}
+
+bool
+__is_console(HANDLE hFile)
+{
+ /* Broken handle can't be terminal */
+ if (hFile == INVALID_HANDLE_VALUE)
+ return false;
+
+ DWORD handleType = GetFileType (hFile);
+
+ /* TTY must be a character device */
+ if (handleType == FILE_TYPE_CHAR)
+ return true;
+
+ DWORD st;
+ /* GetConsoleMode appears to fail when it's not a TTY. In
+ particular, it's what most of our terminal functions
+ assume works, so if it doesn't work for all intents
+ and purposes we're not dealing with a terminal. */
+ if (!GetConsoleMode(hFile, &st)) {
+ /* Clear the error buffer before returning. */
+ SetLastError (ERROR_SUCCESS);
+ return false;
+ }
+
+ return true;
+}
+
+#if !defined(ENABLE_VIRTUAL_TERMINAL_INPUT)
+#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
+#endif
+
+bool
+__set_console_buffering(HANDLE hFile, bool cooked)
+{
+ if (hFile == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ DWORD st;
+ if (!GetConsoleMode(hFile, &st)) {
+ return false;
+ }
+
+ /* According to GetConsoleMode() docs, it is not possible to
+ leave ECHO_INPUT enabled without also having LINE_INPUT,
+ so we have to turn both off here.
+ We toggle ENABLE_VIRTUAL_TERMINAL_INPUT to enable us to receive
+ virtual keyboard keys in ReadConsole. */
+ DWORD flgs = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
+ DWORD enabled = (st & ~flgs) | ENABLE_VIRTUAL_TERMINAL_INPUT;
+ DWORD disabled = (st | ENABLE_LINE_INPUT) & ~ENABLE_VIRTUAL_TERMINAL_INPUT;
+
+
+ return SetConsoleMode(hFile, cooked ? enabled : disabled);
+}
+
+bool
+__set_console_echo(HANDLE hFile, bool on)
+{
+ DWORD st;
+ DWORD flgs = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
+
+ if (hFile == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ return GetConsoleMode(hFile, &st) &&
+ SetConsoleMode(hFile, ( on ? (st | flgs) : (st & ~flgs)));
+}
+
+bool
+__get_console_echo(HANDLE hFile)
+{
+ DWORD st;
+
+ if (hFile == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ return GetConsoleMode(hFile, &st) &&
+ (st & ENABLE_ECHO_INPUT) == ENABLE_ECHO_INPUT;
+}
+
+bool
+__flush_input_console(HANDLE hFile)
+{
+ if ( hFile == INVALID_HANDLE_VALUE )
+ return false;
+
+ /* If the 'handle' isn't connected to a console; treat the flush
+ * operation as a NOP.
+ */
+ DWORD unused;
+ if ( !GetConsoleMode(hFile, &unused) &&
+ GetLastError() == ERROR_INVALID_HANDLE ) {
+ return false;
+ }
+
+ if ( FlushConsoleInputBuffer(hFile) )
+ return true;
+
+ maperrno();
+ return false;
+}
+
+HANDLE_TYPE
+__handle_type (HANDLE hFile)
+{
+ DWORD handleType = GetFileType (hFile);
+ switch (handleType)
+ {
+ case FILE_TYPE_PIPE:
+ {
+ WSAEVENT newEvent = WSACreateEvent();
+ DWORD rc = WSAEventSelect((SOCKET)hFile, newEvent, FD_CLOSE);
+ CloseHandle (newEvent);
+ if (rc == WSAENOTSOCK)
+ return TYPE_SOCKET;
+ else
+ return TYPE_PIPE;
+ }
+ case FILE_TYPE_CHAR:
+ return TYPE_CHAR;
+ case FILE_TYPE_DISK:
+ return TYPE_DISK;
+ case FILE_TYPE_REMOTE:
+ return TYPE_REMOTE;
+ case FILE_TYPE_UNKNOWN:
+ default:
+ return TYPE_UNKNOWN;
+ }
+}
+
+bool
+__close_handle (HANDLE hFile)
+{
+ switch (__handle_type (hFile))
+ {
+ case TYPE_SOCKET:
+ return closesocket ((SOCKET)hFile) == 0;
+ default:
+ return CloseHandle (hFile);
+ }
+}
+
+bool __set_file_pointer (HANDLE hFile, int64_t pos, DWORD moveMethod,
+ int64_t* outPos)
+{
+ LARGE_INTEGER ret;
+ LARGE_INTEGER li;
+ li.QuadPart = pos;
+ bool success = SetFilePointerEx (hFile, li, &ret, moveMethod)
+ != INVALID_SET_FILE_POINTER;
+ *outPos = ret.QuadPart;
+ return success;
+}
+
+int64_t __get_file_pointer (HANDLE hFile)
+{
+ LARGE_INTEGER ret;
+ LARGE_INTEGER pos;
+ pos.QuadPart = 0;
+ if (SetFilePointerEx(hFile, pos, &ret, FILE_CURRENT)
+ == INVALID_SET_FILE_POINTER)
+ return -1;
+
+ return ret.QuadPart;
+}
+
+int64_t __get_file_size (HANDLE hFile)
+{
+ LARGE_INTEGER ret;
+ if (!GetFileSizeEx(hFile, &ret))
+ return -1;
+
+ return ret.QuadPart;
+}
+
+bool __set_file_size (HANDLE hFile, int64_t size)
+{
+ LARGE_INTEGER li;
+ li.QuadPart = size;
+ if(!SetFilePointerEx (hFile, li, NULL, FILE_BEGIN))
+ return false;
+
+ return SetEndOfFile (hFile);
+}
+
+bool __duplicate_handle (HANDLE hFile, HANDLE* hFileDup)
+{
+ switch (__handle_type (hFile))
+ {
+ case TYPE_SOCKET:
+ // should use WSADuplicateSocket
+ return false;
+ default:
+ return DuplicateHandle(GetCurrentProcess(),
+ hFile,
+ GetCurrentProcess(),
+ hFileDup,
+ 0,
+ FALSE,
+ DUPLICATE_SAME_ACCESS);
+ }
+}
+
+bool __set_console_pointer (HANDLE hFile, int64_t pos, DWORD moveMethod,
+ int64_t* outPos)
+{
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if(!GetConsoleScreenBufferInfo (hFile, &info))
+ return false;
+
+ COORD point;
+ switch (moveMethod)
+ {
+ case FILE_END:
+ {
+ int64_t end = info.dwSize.X * info.dwSize.Y;
+ pos = end + pos;
+ point = (COORD) { pos % info.dwSize.X, pos / info.dwSize.X };
+ break;
+ }
+ case FILE_CURRENT:
+ {
+ int64_t current = (info.dwCursorPosition.Y * info.dwSize.X)
+ + info.dwCursorPosition.X;
+ pos = current + pos;
+ point = (COORD) { pos % info.dwSize.X, pos / info.dwSize.X };
+ break;
+ }
+ case FILE_BEGIN:
+ default:
+ point = (COORD) { pos % info.dwSize.X, pos / info.dwSize.X };
+ break;
+ }
+
+ *outPos = pos;
+ return SetConsoleCursorPosition (hFile, point);
+}
+
+int64_t __get_console_pointer (HANDLE hFile)
+{
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if(!GetConsoleScreenBufferInfo (hFile, &info))
+ return -1;
+
+ return (info.dwCursorPosition.Y * info.dwSize.X) + info.dwCursorPosition.X;
+}
+
+int64_t __get_console_buffer_size (HANDLE hFile)
+{
+ CONSOLE_SCREEN_BUFFER_INFO ret;
+ if (!GetConsoleScreenBufferInfo(hFile, &ret))
+ return -1;
+
+ return ret.dwSize.X * ret.dwSize.Y;
+}
+
+bool __set_console_buffer_size (HANDLE hFile, int64_t size)
+{
+ CONSOLE_SCREEN_BUFFER_INFO ret;
+ if (!GetConsoleScreenBufferInfo(hFile, &ret))
+ return false;
+
+ COORD sz = {ret.dwSize.X, (int)ceil(size / ret.dwSize.X)};
+ return SetConsoleScreenBufferSize (hFile, sz);
+}
+