+/* Atomically mark current thread as (probably) doing synchronous I/O
+ * on handle, if no cancellation is requested yet (and return TRUE),
+ * otherwise clear thread's I/O cancellation flag and return false.
+ */
+static
+boolean io_begin_interruptible(HANDLE handle)
+{
+ /* No point in doing it unless OS supports cancellation from other
+ * threads */
+ if (!ptr_CancelIoEx)
+ return 1;
+
+ if (!__sync_bool_compare_and_swap(&this_thread->synchronous_io_handle_and_flag,
+ 0, handle)) {
+ ResetEvent(this_thread->private_events.events[0]);
+ this_thread->synchronous_io_handle_and_flag = 0;
+ return 0;
+ }
+ return 1;
+}
+
+static pthread_mutex_t interrupt_io_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Unmark current thread as (probably) doing synchronous I/O; if an
+ * I/O cancellation was requested, postpone it until next
+ * io_begin_interruptible */
+static void
+io_end_interruptible(HANDLE handle)
+{
+ if (!ptr_CancelIoEx)
+ return;
+ pthread_mutex_lock(&interrupt_io_lock);
+ __sync_bool_compare_and_swap(&this_thread->synchronous_io_handle_and_flag,
+ handle, 0);
+ pthread_mutex_unlock(&interrupt_io_lock);
+}
+
+/* Documented limit for ReadConsole/WriteConsole is 64K bytes.
+ Real limit observed on W2K-SP3 is somewhere in between 32KiB and 64Kib...
+*/
+#define MAX_CONSOLE_TCHARS 16384
+
+int
+win32_write_unicode_console(HANDLE handle, void * buf, int count)
+{
+ DWORD written = 0;
+ DWORD nchars;
+ BOOL result;
+ nchars = count>>1;
+ if (nchars>MAX_CONSOLE_TCHARS) nchars = MAX_CONSOLE_TCHARS;
+
+ if (!io_begin_interruptible(handle)) {
+ errno = EINTR;
+ return -1;
+ }
+ result = WriteConsoleW(handle,buf,nchars,&written,NULL);
+ io_end_interruptible(handle);
+
+ if (result) {
+ if (!written) {
+ errno = EINTR;
+ return -1;
+ } else {
+ return 2*written;
+ }
+ } else {
+ DWORD err = GetLastError();
+ odxprint(io,"WriteConsole fails => %u\n", err);
+ errno = (err==ERROR_OPERATION_ABORTED ? EINTR : EIO);
+ return -1;
+ }
+}
+
+/*
+ * (AK writes:)
+ *
+ * It may be unobvious, but (probably) the most straightforward way of
+ * providing some sane CL:LISTEN semantics for line-mode console
+ * channel requires _dedicated input thread_.
+ *
+ * LISTEN should return true iff the next (READ-CHAR) won't have to
+ * wait. As our console may be shared with another process, entirely
+ * out of our control, looking at the events in PeekConsoleEvent
+ * result (and searching for #\Return) doesn't cut it.
+ *
+ * We decided that console input thread must do something smarter than
+ * a bare loop of continuous ReadConsoleW(). On Unix, user experience
+ * with the terminal is entirely unaffected by the fact that some
+ * process does (or doesn't) call read(); the situation on MS Windows
+ * is different.
+ *
+ * Echo output and line editing present on MS Windows while some
+ * process is waiting in ReadConsole(); otherwise all input events are
+ * buffered. If our thread were calling ReadConsole() all the time, it
+ * would feel like Unix cooked mode.
+ *
+ * But we don't write a Unix emulator here, even if it sometimes feels
+ * like that; therefore preserving this aspect of console I/O seems a
+ * good thing to us.
+ *
+ * LISTEN itself becomes trivial with dedicated input thread, but the
+ * goal stated above -- provide `native' user experience with blocked
+ * console -- don't play well with this trivial implementation.
+ *
+ * What's currently implemented is a compromise, looking as something
+ * in between Unix cooked mode and Win32 line mode.
+ *
+ * 1. As long as no console I/O function is called (incl. CL:LISTEN),
+ * console looks `blocked': no echo, no line editing.
+ *
+ * 2. (READ-CHAR), (READ-SEQUENCE) and other functions doing real
+ * input result in the ReadConsole request (in a dedicated thread);
+ *
+ * 3. Once ReadConsole is called, it is not cancelled in the
+ * middle. In line mode, it returns when <Enter> key is hit (or
+ * something like that happens). Therefore, if line editing and echo
+ * output had a chance to happen, console won't look `blocked' until
+ * the line is entered (even if line input was triggered by
+ * (READ-CHAR)).
+ *
+ * 4. LISTEN may request ReadConsole too (if no other thread is
+ * reading the console and no data are queued). It's the only case
+ * when the console becomes `unblocked' without any actual input
+ * requested by Lisp code. LISTEN check if there is at least one
+ * input event in PeekConsole queue; unless there is such an event,
+ * ReadConsole is not triggered by LISTEN.
+ *
+ * 5. Console-reading Lisp thread now may be interrupted immediately;
+ * ReadConsole call itself, however, continues until the line is
+ * entered.
+ */
+
+struct {
+ WCHAR buffer[MAX_CONSOLE_TCHARS];
+ DWORD head, tail;
+ pthread_mutex_t lock;
+ pthread_cond_t cond_has_data;
+ pthread_cond_t cond_has_client;
+ pthread_t thread;
+ boolean initialized;
+ HANDLE handle;
+ boolean in_progress;
+} ttyinput = {.lock = PTHREAD_MUTEX_INITIALIZER};
+
+static void*
+tty_read_line_server()
+{
+ pthread_mutex_lock(&ttyinput.lock);
+ while (ttyinput.handle) {
+ DWORD nchars;
+ BOOL ok;
+
+ while (!ttyinput.in_progress)
+ pthread_cond_wait(&ttyinput.cond_has_client,&ttyinput.lock);
+
+ pthread_mutex_unlock(&ttyinput.lock);
+
+ ok = ReadConsoleW(ttyinput.handle,
+ &ttyinput.buffer[ttyinput.tail],
+ MAX_CONSOLE_TCHARS-ttyinput.tail,
+ &nchars,NULL);
+
+ pthread_mutex_lock(&ttyinput.lock);
+
+ if (ok) {
+ ttyinput.tail += nchars;
+ pthread_cond_broadcast(&ttyinput.cond_has_data);
+ }
+ ttyinput.in_progress = 0;
+ }
+ pthread_mutex_unlock(&ttyinput.lock);
+ return NULL;
+}
+
+static boolean
+tty_maybe_initialize_unlocked(HANDLE handle)
+{
+ if (!ttyinput.initialized) {
+ if (!DuplicateHandle(GetCurrentProcess(),handle,
+ GetCurrentProcess(),&ttyinput.handle,
+ 0,FALSE,DUPLICATE_SAME_ACCESS)) {
+ return 0;
+ }
+ pthread_cond_init(&ttyinput.cond_has_data,NULL);
+ pthread_cond_init(&ttyinput.cond_has_client,NULL);
+ pthread_create(&ttyinput.thread,NULL,tty_read_line_server,NULL);
+ ttyinput.initialized = 1;
+ }
+ return 1;
+}
+
+boolean
+win32_tty_listen(HANDLE handle)
+{
+ boolean result = 0;
+ INPUT_RECORD ir;
+ DWORD nevents;
+ pthread_mutex_lock(&ttyinput.lock);
+ if (!tty_maybe_initialize_unlocked(handle))
+ result = 0;
+
+ if (ttyinput.in_progress) {
+ result = 0;
+ } else {
+ if (ttyinput.head != ttyinput.tail) {
+ result = 1;
+ } else {
+ if (PeekConsoleInput(ttyinput.handle,&ir,1,&nevents) && nevents) {
+ ttyinput.in_progress = 1;
+ pthread_cond_broadcast(&ttyinput.cond_has_client);
+ }
+ }
+ }
+ pthread_mutex_unlock(&ttyinput.lock);
+ return result;
+}
+
+static int
+tty_read_line_client(HANDLE handle, void* buf, int count)
+{
+ int result = 0;
+ int nchars = count / sizeof(WCHAR);
+ sigset_t pendset;
+
+ if (!nchars)
+ return 0;
+ if (nchars>MAX_CONSOLE_TCHARS)
+ nchars=MAX_CONSOLE_TCHARS;
+
+ count = nchars*sizeof(WCHAR);
+
+ pthread_mutex_lock(&ttyinput.lock);
+
+ if (!tty_maybe_initialize_unlocked(handle)) {
+ result = -1;
+ errno = EIO;
+ goto unlock;
+ }
+
+ while (!result) {
+ while (ttyinput.head == ttyinput.tail) {
+ if (!io_begin_interruptible(ttyinput.handle)) {
+ ttyinput.in_progress = 0;
+ result = -1;
+ errno = EINTR;
+ goto unlock;
+ } else {
+ if (!ttyinput.in_progress) {
+ /* We are to wait */
+ ttyinput.in_progress=1;
+ /* wake console reader */
+ pthread_cond_broadcast(&ttyinput.cond_has_client);
+ }
+ pthread_cond_wait(&ttyinput.cond_has_data, &ttyinput.lock);
+ io_end_interruptible(ttyinput.handle);
+ }
+ }
+ result = sizeof(WCHAR)*(ttyinput.tail-ttyinput.head);
+ if (result > count) {
+ result = count;
+ }
+ if (result) {
+ if (result > 0) {
+ DWORD nch,offset = 0;
+ LPWSTR ubuf = buf;
+
+ memcpy(buf,&ttyinput.buffer[ttyinput.head],count);
+ ttyinput.head += (result / sizeof(WCHAR));
+ if (ttyinput.head == ttyinput.tail)
+ ttyinput.head = ttyinput.tail = 0;
+
+ for (nch=0;nch<result/sizeof(WCHAR);++nch) {
+ if (ubuf[nch]==13) {
+ ++offset;
+ } else {
+ ubuf[nch-offset]=ubuf[nch];
+ }
+ }
+ result-=offset*sizeof(WCHAR);
+
+ }
+ } else {
+ result = -1;
+ ttyinput.head = ttyinput.tail = 0;
+ errno = EIO;
+ }
+ }
+unlock:
+ pthread_mutex_unlock(&ttyinput.lock);
+ return result;
+}
+
+int
+win32_read_unicode_console(HANDLE handle, void* buf, int count)
+{
+
+ int result;
+ result = tty_read_line_client(handle,buf,count);
+ return result;
+}
+
+boolean
+win32_maybe_interrupt_io(void* thread)
+{
+ struct thread *th = thread;
+ boolean done = 0;
+ if (ptr_CancelIoEx) {
+ pthread_mutex_lock(&interrupt_io_lock);
+ HANDLE h = (HANDLE)
+ InterlockedExchangePointer((volatile LPVOID *)
+ &th->synchronous_io_handle_and_flag,
+ (LPVOID)INVALID_HANDLE_VALUE);
+ if (h && (h!=INVALID_HANDLE_VALUE)) {
+ if (console_handle_p(h)) {
+ pthread_mutex_lock(&ttyinput.lock);
+ pthread_cond_broadcast(&ttyinput.cond_has_data);
+ pthread_mutex_unlock(&ttyinput.lock);
+ }
+ if (ptr_CancelSynchronousIo) {
+ pthread_mutex_lock(&th->os_thread->fiber_lock);
+ done = !!ptr_CancelSynchronousIo(th->os_thread->fiber_group->handle);
+ pthread_mutex_unlock(&th->os_thread->fiber_lock);
+ }
+ done |= !!ptr_CancelIoEx(h,NULL);
+ }
+ pthread_mutex_unlock(&interrupt_io_lock);
+ }
+ return done;
+}
+
+static const LARGE_INTEGER zero_large_offset = {.QuadPart = 0LL};
+
+int
+win32_unix_write(HANDLE handle, void * buf, int count)
+{
+ DWORD written_bytes;
+ OVERLAPPED overlapped;
+ struct thread * self = arch_os_get_current_thread();
+ BOOL waitInGOR;
+ LARGE_INTEGER file_position;
+ BOOL seekable;
+ BOOL ok;
+
+ if (console_handle_p(handle))
+ return win32_write_unicode_console(handle,buf,count);
+
+ overlapped.hEvent = self->private_events.events[0];
+ seekable = SetFilePointerEx(handle,
+ zero_large_offset,
+ &file_position,
+ FILE_CURRENT);
+ if (seekable) {
+ overlapped.Offset = file_position.LowPart;
+ overlapped.OffsetHigh = file_position.HighPart;
+ } else {
+ overlapped.Offset = 0;
+ overlapped.OffsetHigh = 0;
+ }
+ if (!io_begin_interruptible(handle)) {
+ errno = EINTR;
+ return -1;
+ }
+ ok = WriteFile(handle, buf, count, &written_bytes, &overlapped);
+ io_end_interruptible(handle);
+
+ if (ok) {
+ goto done_something;
+ } else {
+ DWORD errorCode = GetLastError();
+ if (errorCode==ERROR_OPERATION_ABORTED) {
+ GetOverlappedResult(handle,&overlapped,&written_bytes,FALSE);
+ errno = EINTR;
+ return -1;
+ }
+ if (errorCode!=ERROR_IO_PENDING) {
+ errno = EIO;
+ return -1;
+ } else {
+ if(WaitForMultipleObjects(2,self->private_events.events,
+ FALSE,INFINITE) != WAIT_OBJECT_0) {
+ CancelIo(handle);
+ waitInGOR = TRUE;
+ } else {
+ waitInGOR = FALSE;
+ }
+ if (!GetOverlappedResult(handle,&overlapped,&written_bytes,waitInGOR)) {
+ if (GetLastError()==ERROR_OPERATION_ABORTED) {
+ errno = EINTR;
+ } else {
+ errno = EIO;
+ }
+ return -1;
+ } else {
+ goto done_something;
+ }
+ }
+ }
+ done_something:
+ if (seekable) {
+ file_position.QuadPart += written_bytes;
+ SetFilePointerEx(handle,file_position,NULL,FILE_BEGIN);
+ }
+ return written_bytes;
+}
+
+int
+win32_unix_read(HANDLE handle, void * buf, int count)
+{
+ OVERLAPPED overlapped = {.Internal=0};
+ DWORD read_bytes = 0;
+ struct thread * self = arch_os_get_current_thread();
+ DWORD errorCode = 0;
+ BOOL waitInGOR = FALSE;
+ BOOL ok = FALSE;
+ LARGE_INTEGER file_position;
+ BOOL seekable;
+
+ if (console_handle_p(handle))
+ return win32_read_unicode_console(handle,buf,count);
+
+ overlapped.hEvent = self->private_events.events[0];
+ /* If it has a position, we won't try overlapped */
+ seekable = SetFilePointerEx(handle,
+ zero_large_offset,
+ &file_position,
+ FILE_CURRENT);
+ if (seekable) {
+ overlapped.Offset = file_position.LowPart;
+ overlapped.OffsetHigh = file_position.HighPart;
+ } else {
+ overlapped.Offset = 0;
+ overlapped.OffsetHigh = 0;
+ }
+ if (!io_begin_interruptible(handle)) {
+ errno = EINTR;
+ return -1;
+ }
+ ok = ReadFile(handle,buf,count,&read_bytes, &overlapped);
+ io_end_interruptible(handle);
+ if (ok) {
+ /* immediately */
+ goto done_something;
+ } else {
+ errorCode = GetLastError();
+ if (errorCode == ERROR_HANDLE_EOF ||
+ errorCode == ERROR_BROKEN_PIPE ||
+ errorCode == ERROR_NETNAME_DELETED) {
+ read_bytes = 0;
+ goto done_something;
+ }
+ if (errorCode==ERROR_OPERATION_ABORTED) {
+ GetOverlappedResult(handle,&overlapped,&read_bytes,FALSE);
+ errno = EINTR;
+ return -1;
+ }
+ if (errorCode!=ERROR_IO_PENDING) {
+ /* is it some _real_ error? */
+ errno = EIO;
+ return -1;
+ } else {
+ int ret;
+ if( (ret = WaitForMultipleObjects(2,self->private_events.events,
+ FALSE,INFINITE)) != WAIT_OBJECT_0) {
+ CancelIo(handle);
+ waitInGOR = TRUE;
+ /* Waiting for IO only */
+ } else {
+ waitInGOR = FALSE;
+ }
+ ok = GetOverlappedResult(handle,&overlapped,&read_bytes,waitInGOR);
+ if (!ok) {
+ errorCode = GetLastError();
+ if (errorCode == ERROR_HANDLE_EOF ||
+ errorCode == ERROR_BROKEN_PIPE ||
+ errorCode == ERROR_NETNAME_DELETED) {
+ read_bytes = 0;
+ goto done_something;
+ } else {
+ if (errorCode == ERROR_OPERATION_ABORTED)
+ errno = EINTR; /* that's it. */
+ else
+ errno = EIO; /* something unspecific */
+ return -1;
+ }
+ } else
+ goto done_something;
+ }
+ }
+ done_something:
+ if (seekable) {
+ file_position.QuadPart += read_bytes;
+ SetFilePointerEx(handle,file_position,NULL,FILE_BEGIN);
+ }
+ return read_bytes;
+}
+
+/* We used to have a scratch() function listing all symbols needed by
+ * Lisp. Much rejoicing commenced upon its removal. However, I would
+ * like cold init to fail aggressively when encountering unused symbols.
+ * That poses a problem, however, since our C code no longer includes
+ * any references to symbols in ws2_32.dll, and hence the linker
+ * completely ignores our request to reference it (--no-as-needed does
+ * not work). Warm init would later load the DLLs explicitly, but then
+ * it's too late for an early sanity check. In the unfortunate spirit
+ * of scratch(), continue to reference some required DLLs explicitly by
+ * means of one scratch symbol per DLL.
+ */