From c06af229dcb066ba6c52f5866daf2e415a99a06e Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Tue, 27 Sep 2011 16:16:59 +0200 Subject: [PATCH] Upgrade libuv to 2c0fca9a41 --- deps/uv/common.gypi | 5 + deps/uv/include/uv-private/uv-win.h | 28 +- deps/uv/src/unix/cares.c | 1 + deps/uv/src/unix/fs.c | 19 +- deps/uv/src/unix/udp.c | 1 + deps/uv/src/win/core.c | 3 + deps/uv/src/win/handle.c | 33 + deps/uv/src/win/internal.h | 28 + deps/uv/src/win/pipe.c | 3 + deps/uv/src/win/req.c | 6 + deps/uv/src/win/stream.c | 13 +- deps/uv/src/win/tcp.c | 4 +- deps/uv/src/win/tty.c | 1558 ++++++++++++++++++++++++++- deps/uv/test/test-list.h | 12 + deps/uv/test/test-tcp-close.c | 47 + deps/uv/test/test-tcp-write-error.c | 64 +- deps/uv/test/test-timer.c | 40 + 17 files changed, 1811 insertions(+), 54 deletions(-) diff --git a/deps/uv/common.gypi b/deps/uv/common.gypi index 9a0be4dc394..373c3aa9807 100644 --- a/deps/uv/common.gypi +++ b/deps/uv/common.gypi @@ -32,6 +32,11 @@ 'LinkIncremental': 2, # enable incremental linking }, }, + 'conditions': [ + ['OS != "win"', { + 'defines': [ 'EV_VERIFY=2' ], + }], + ] }, 'Release': { 'defines': [ 'NDEBUG' ], diff --git a/deps/uv/include/uv-private/uv-win.h b/deps/uv/include/uv-private/uv-win.h index f203d672891..372d65c67eb 100644 --- a/deps/uv/include/uv-private/uv-win.h +++ b/deps/uv/include/uv-private/uv-win.h @@ -117,6 +117,7 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); HANDLE pipeHandle; \ struct uv_pipe_accept_s* next_pending; \ } uv_pipe_accept_t; \ + \ typedef struct uv_tcp_accept_s { \ UV_REQ_FIELDS \ SOCKET accept_socket; \ @@ -181,6 +182,31 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); struct { uv_pipe_connection_fields }; \ }; +/* TODO: put the parser states in an union - TTY handles are always */ +/* half-duplex so read-state can safely overlap write-state. */ +#define UV_TTY_PRIVATE_FIELDS \ + HANDLE handle; \ + HANDLE read_line_handle; \ + uv_buf_t read_line_buffer; \ + HANDLE read_raw_wait; \ + DWORD original_console_mode; \ + /* Fields used for translating win */ \ + /* keystrokes into vt100 characters */ \ + char last_key[8]; \ + unsigned char last_key_offset; \ + unsigned char last_key_len; \ + INPUT_RECORD last_input_record; \ + WCHAR last_utf16_high_surrogate; \ + /* utf8-to-utf16 conversion state */ \ + unsigned char utf8_bytes_left; \ + unsigned int utf8_codepoint; \ + /* eol conversion state */ \ + unsigned char previous_eol; \ + /* ansi parser state */ \ + unsigned char ansi_parser_state; \ + unsigned char ansi_csi_argc; \ + unsigned short ansi_csi_argv[4]; + #define UV_TIMER_PRIVATE_FIELDS \ RB_ENTRY(uv_timer_s) tree_entry; \ int64_t due; \ @@ -273,8 +299,6 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); int is_path_dir; \ char* buffer; -#define UV_TTY_PRIVATE_FIELDS /* empty */ - int uv_utf16_to_utf8(const wchar_t* utf16Buffer, size_t utf16Size, char* utf8Buffer, size_t utf8Size); int uv_utf8_to_utf16(const char* utf8Buffer, wchar_t* utf16Buffer, diff --git a/deps/uv/src/unix/cares.c b/deps/uv/src/unix/cares.c index a2466f59e6f..b2bc16f4c79 100644 --- a/deps/uv/src/unix/cares.c +++ b/deps/uv/src/unix/cares.c @@ -67,6 +67,7 @@ static uv_ares_task_t* uv__ares_task_create(int fd) { if (h == NULL) { uv_fatal_error(ENOMEM, "malloc"); + return NULL; } h->sock = fd; diff --git a/deps/uv/src/unix/fs.c b/deps/uv/src/unix/fs.c index ec24e8b3fa3..f1974ca2d63 100644 --- a/deps/uv/src/unix/fs.c +++ b/deps/uv/src/unix/fs.c @@ -149,19 +149,20 @@ static int uv__fs_after(eio_req* eio) { case UV_FS_READLINK: if (req->result == -1) { req->ptr = NULL; - } else { - assert(req->result > 0); - - if ((name = realloc(req->eio->ptr2, req->result + 1)) == NULL) { - /* Not enough memory. Reuse buffer, chop off last byte. */ - name = req->eio->ptr2; - req->result--; - } + break; + } + assert(req->result > 0); + /* Make zero-terminated copy of req->eio->ptr2 */ + if ((req->ptr = name = malloc(req->result + 1))) { + memcpy(name, req->eio->ptr2, req->result); name[req->result] = '\0'; - req->ptr = name; req->result = 0; } + else { + req->errorno = ENOMEM; + req->result = -1; + } break; default: diff --git a/deps/uv/src/unix/udp.c b/deps/uv/src/unix/udp.c index 20e5f3798b9..bbd645f5149 100644 --- a/deps/uv/src/unix/udp.c +++ b/deps/uv/src/unix/udp.c @@ -301,6 +301,7 @@ static int uv__udp_bind(uv_udp_t* handle, saved_errno = errno; status = -1; + fd = -1; /* Check for bad flags. */ if (flags & ~UV_UDP_IPV6ONLY) { diff --git a/deps/uv/src/win/core.c b/deps/uv/src/win/core.c index 3211bbf2afd..cb4a28b8238 100644 --- a/deps/uv/src/win/core.c +++ b/deps/uv/src/win/core.c @@ -48,6 +48,9 @@ static void uv_init(void) { /* Initialize FS */ uv_fs_init(); + + /* Initialize console */ + uv_console_init(); } diff --git a/deps/uv/src/win/handle.c b/deps/uv/src/win/handle.c index ab4f64bc5ae..b67139cbc6a 100644 --- a/deps/uv/src/win/handle.c +++ b/deps/uv/src/win/handle.c @@ -20,11 +20,36 @@ */ #include +#include #include "uv.h" #include "internal.h" +uv_handle_type uv_guess_handle(uv_file file) { + HANDLE handle = (HANDLE) _get_osfhandle(file); + DWORD mode; + + switch (GetFileType(handle)) { + case FILE_TYPE_CHAR: + if (GetConsoleMode(handle, &mode)) { + return UV_TTY; + } else { + return UV_UNKNOWN_HANDLE; + } + + case FILE_TYPE_PIPE: + return UV_NAMED_PIPE; + + case FILE_TYPE_DISK: + return UV_FILE; + + default: + return UV_UNKNOWN_HANDLE; + } +} + + int uv_is_active(uv_handle_t* handle) { switch (handle->type) { case UV_TIMER: @@ -80,6 +105,10 @@ void uv_close(uv_handle_t* handle, uv_close_cb cb) { } return; + case UV_TTY: + uv_tty_close((uv_tty_t*) handle); + return; + case UV_UDP: udp = (uv_udp_t*) handle; uv_udp_recv_stop(udp); @@ -159,6 +188,10 @@ void uv_process_endgames(uv_loop_t* loop) { uv_pipe_endgame(loop, (uv_pipe_t*) handle); break; + case UV_TTY: + uv_tty_endgame(loop, (uv_tty_t*) handle); + break; + case UV_UDP: uv_udp_endgame(loop, (uv_udp_t*) handle); break; diff --git a/deps/uv/src/win/internal.h b/deps/uv/src/win/internal.h index 87a64eda1b2..fa20e4660cf 100644 --- a/deps/uv/src/win/internal.h +++ b/deps/uv/src/win/internal.h @@ -64,6 +64,7 @@ void uv_process_timers(uv_loop_t* loop); #define UV_HANDLE_UV_ALLOCED 0x20000 #define UV_HANDLE_SYNC_BYPASS_IOCP 0x40000 #define UV_HANDLE_ZERO_READ 0x80000 +#define UV_HANDLE_TTY_RAW 0x100000 void uv_want_endgame(uv_loop_t* loop, uv_handle_t* handle); void uv_process_endgames(uv_loop_t* loop); @@ -173,6 +174,33 @@ void uv_process_pipe_connect_req(uv_loop_t* loop, uv_pipe_t* handle, void uv_process_pipe_shutdown_req(uv_loop_t* loop, uv_pipe_t* handle, uv_shutdown_t* req); + +/* + * TTY + */ +void uv_console_init(); + +int uv_tty_read_start(uv_tty_t* handle, uv_alloc_cb alloc_cb, + uv_read_cb read_cb); +int uv_tty_read_stop(uv_tty_t* handle); +int uv_tty_write(uv_loop_t* loop, uv_write_t* req, uv_tty_t* handle, + uv_buf_t bufs[], int bufcnt, uv_write_cb cb); +void uv_tty_close(uv_tty_t* handle); + +void uv_process_tty_read_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* req); +void uv_process_tty_write_req(uv_loop_t* loop, uv_tty_t* handle, + uv_write_t* req); +/* TODO: remove me */ +void uv_process_tty_accept_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* raw_req); +/* TODO: remove me */ +void uv_process_tty_connect_req(uv_loop_t* loop, uv_tty_t* handle, + uv_connect_t* req); + +void uv_tty_endgame(uv_loop_t* loop, uv_tty_t* handle); + + /* * Loop watchers */ diff --git a/deps/uv/src/win/pipe.c b/deps/uv/src/win/pipe.c index 7832c6a41af..8d7891539a5 100644 --- a/deps/uv/src/win/pipe.c +++ b/deps/uv/src/win/pipe.c @@ -231,6 +231,7 @@ void uv_pipe_endgame(uv_loop_t* loop, uv_pipe_t* handle) { if (result) { /* Mark the handle as shut now to avoid going through this again. */ handle->flags |= UV_HANDLE_SHUT; + return; } else { /* Failure. */ @@ -678,6 +679,8 @@ static void uv_pipe_queue_read(uv_loop_t* loop, uv_pipe_t* handle) { /* Make this req pending reporting an error. */ SET_REQ_ERROR(req, WSAGetLastError()); uv_insert_pending_req(loop, req); + + handle->flags |= UV_HANDLE_READ_PENDING; handle->reqs_pending++; return; } diff --git a/deps/uv/src/win/req.c b/deps/uv/src/win/req.c index a0a6e03dc60..dd2b38809e9 100644 --- a/deps/uv/src/win/req.c +++ b/deps/uv/src/win/req.c @@ -87,6 +87,12 @@ static uv_req_t* uv_remove_pending_req(uv_loop_t* loop) { req); \ break; \ \ + case UV_TTY: \ + uv_process_tty_##method##_req(loop, \ + (uv_tty_t*) ((req)->handle_at), \ + req); \ + break; \ + \ default: \ assert(0); \ } \ diff --git a/deps/uv/src/win/stream.c b/deps/uv/src/win/stream.c index 26964110fce..476496759f0 100644 --- a/deps/uv/src/win/stream.c +++ b/deps/uv/src/win/stream.c @@ -83,6 +83,8 @@ int uv_read_start(uv_stream_t* handle, uv_alloc_cb alloc_cb, return uv_tcp_read_start((uv_tcp_t*)handle, alloc_cb, read_cb); case UV_NAMED_PIPE: return uv_pipe_read_start((uv_pipe_t*)handle, alloc_cb, read_cb); + case UV_TTY: + return uv_tty_read_start((uv_tty_t*) handle, alloc_cb, read_cb); default: assert(0); return -1; @@ -91,9 +93,12 @@ int uv_read_start(uv_stream_t* handle, uv_alloc_cb alloc_cb, int uv_read_stop(uv_stream_t* handle) { - handle->flags &= ~UV_HANDLE_READING; - - return 0; + if (handle->type = UV_TTY) { + return uv_tty_read_stop((uv_tty_t*) handle); + } else { + handle->flags &= ~UV_HANDLE_READING; + return 0; + } } @@ -106,6 +111,8 @@ int uv_write(uv_write_t* req, uv_stream_t* handle, uv_buf_t bufs[], int bufcnt, return uv_tcp_write(loop, req, (uv_tcp_t*) handle, bufs, bufcnt, cb); case UV_NAMED_PIPE: return uv_pipe_write(loop, req, (uv_pipe_t*) handle, bufs, bufcnt, cb); + case UV_TTY: + return uv_tty_write(loop, req, (uv_tty_t*) handle, bufs, bufcnt, cb); default: assert(0); uv_set_sys_error(loop, WSAEINVAL); diff --git a/deps/uv/src/win/tcp.c b/deps/uv/src/win/tcp.c index ebd83534983..783d5fb9a28 100644 --- a/deps/uv/src/win/tcp.c +++ b/deps/uv/src/win/tcp.c @@ -128,7 +128,9 @@ void uv_tcp_endgame(uv_loop_t* loop, uv_tcp_t* handle) { } handle->shutdown_req->cb(handle->shutdown_req, status); } - handle->reqs_pending--; + + DECREASE_PENDING_REQ_COUNT(handle); + return; } if (handle->flags & UV_HANDLE_CLOSING && diff --git a/deps/uv/src/win/tty.c b/deps/uv/src/win/tty.c index a4a46829643..1a4bf11a335 100644 --- a/deps/uv/src/win/tty.c +++ b/deps/uv/src/win/tty.c @@ -19,43 +19,1573 @@ * IN THE SOFTWARE. */ +#include +#include +#include +#include + #include "uv.h" +#include "../uv-common.h" #include "internal.h" -#include + +#define UNICODE_REPLACEMENT_CHARACTER (0xfffd) + +#define ANSI_NORMAL 0x00 +#define ANSI_ESCAPE_SEEN 0x02 +#define ANSI_CSI 0x04 +#define ANSI_ST_CONTROL 0x08 +#define ANSI_IGNORE 0x10 +#define ANSI_IN_ARG 0x20 +#define ANSI_IN_STRING 0x40 +#define ANSI_BACKSLASH_SEEN 0x80 + + +static void uv_tty_update_virtual_window(CONSOLE_SCREEN_BUFFER_INFO* info); + + +/* Null uv_buf_t */ +static const uv_buf_t uv_null_buf_ = { 0, NULL }; + + +/* + * The console virtual window. + * + * Normally cursor movement in windows is relative to the console screen buffer, + * e.g. the application is allowed to overwrite the 'history'. This is very + * inconvenient, it makes absolute cursor movement pretty useless. There is + * also the concept of 'client rect' which is defined by the actual size of + * the console window and the scroll position of the screen buffer, but it's + * very volatile because it changes when the user scrolls. + * + * To make cursor movement behave sensibly we define a virtual window to which + * cursor movement is confined. The virtual window is always as wide as the + * console screen buffer, but it's height is defined by the size of the + * console window. The top of the virtual window aligns with the position + * of the caret when the first stdout/err handle is created, unless that would + * mean that it would extend beyond the bottom of the screen buffer - in that + * that case it's located as far down as possible. + * + * When the user writes a long text or many newlines, such that the output + * reaches beyond the bottom of the virtual window, the virtual window is + * shifted downwards, but not resized. + * + * Since all tty i/o happens on the same console, this window is shared + * between all stdout/stderr handles. + */ + +static int uv_tty_virtual_offset = -1; +static int uv_tty_virtual_height = -1; +static int uv_tty_virtual_width = -1; + +static CRITICAL_SECTION uv_tty_output_lock; + + +void uv_console_init() { + InitializeCriticalSection(&uv_tty_output_lock); +} int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, uv_file fd) { - assert(0 && "implement me"); - return -1; + HANDLE win_handle; + CONSOLE_SCREEN_BUFFER_INFO info; + + win_handle = (HANDLE) _get_osfhandle(fd); + if (win_handle == INVALID_HANDLE_VALUE) { + uv_set_sys_error(loop, ERROR_INVALID_HANDLE); + return -1; + } + + if (!GetConsoleMode(win_handle, &tty->original_console_mode)) { + uv_set_sys_error(loop, GetLastError()); + return -1; + } + + /* Initialize virtual window size; if it fails, assume that this is stdin. */ + if (GetConsoleScreenBufferInfo(win_handle, &info)) { + EnterCriticalSection(&uv_tty_output_lock); + uv_tty_update_virtual_window(&info); + LeaveCriticalSection(&uv_tty_output_lock); + } + + uv_stream_init(loop, (uv_stream_t*) tty); + uv_connection_init((uv_stream_t*) tty); + + tty->type = UV_TTY; + tty->handle = win_handle; + tty->read_line_handle = NULL; + tty->read_line_buffer = uv_null_buf_; + tty->read_raw_wait = NULL; + tty->reqs_pending = 0; + tty->flags |= UV_HANDLE_BOUND; + + /* Init keycode-to-vt100 mapper state. */ + tty->last_key_len = 0; + tty->last_key_offset = 0; + tty->last_utf16_high_surrogate = 0; + memset(&tty->last_input_record, 0, sizeof tty->last_input_record); + + /* Init utf8-to-utf16 conversion state. */ + tty->utf8_bytes_left = 0; + tty->utf8_codepoint = 0; + + /* Initialize eol conversion state */ + tty->previous_eol = 0; + + /* Init ANSI parser state. */ + tty->ansi_parser_state = ANSI_NORMAL; + + return 0; } int uv_tty_set_mode(uv_tty_t* tty, int mode) { - assert(0 && "implement me"); - return -1; + DWORD flags = 0; + unsigned char was_reading; + uv_alloc_cb alloc_cb; + uv_read_cb read_cb; + + if (!!mode == !!(tty->flags & UV_HANDLE_TTY_RAW)) { + return 0; + } + + if (tty->original_console_mode & ENABLE_QUICK_EDIT_MODE) { + flags = ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS; + } + + if (mode) { + /* Raw input */ + flags |= ENABLE_WINDOW_INPUT; + } else { + /* Line-buffered mode. */ + flags |= ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | + ENABLE_EXTENDED_FLAGS | ENABLE_PROCESSED_INPUT; + } + + if (!SetConsoleMode(tty->handle, flags)) { + uv_set_sys_error(tty->loop, GetLastError()); + return -1; + } + + /* If currently reading, stop, and restart reading. */ + if (tty->flags & UV_HANDLE_READING) { + was_reading = 1; + alloc_cb = tty->alloc_cb; + read_cb = tty->read_cb; + + if (was_reading && uv_tty_read_stop(tty) != 0) { + return -1; + } + } else { + was_reading = 0; + } + + /* Update flag. */ + tty->flags &= ~UV_HANDLE_TTY_RAW; + tty->flags |= mode ? UV_HANDLE_TTY_RAW : 0; + + /* If we just stopped reading, restart. */ + if (was_reading && uv_tty_read_start(tty, alloc_cb, read_cb) != 0) { + return -1; + } + + return 0; } int uv_is_tty(uv_file file) { + DWORD result; + return GetConsoleMode((HANDLE) _get_osfhandle(file), &result) != 0; } int uv_tty_get_winsize(uv_tty_t* tty, int* width, int* height) { - assert(0 && "implement me"); - return -1; + CONSOLE_SCREEN_BUFFER_INFO info; + + if (!GetConsoleScreenBufferInfo(tty->handle, &info)) { + uv_set_sys_error(tty->loop, GetLastError()); + return -1; + } + + EnterCriticalSection(&uv_tty_output_lock); + uv_tty_update_virtual_window(&info); + LeaveCriticalSection(&uv_tty_output_lock); + + *width = uv_tty_virtual_width; + *height = uv_tty_virtual_height; + + return 0; } -uv_handle_type uv_guess_handle(uv_file file) { - DWORD result; - int r = GetConsoleMode((HANDLE)_get_osfhandle(file), &result); +static void CALLBACK uv_tty_post_raw_read(void* data, BOOLEAN didTimeout) { + uv_loop_t* loop; + uv_tty_t* handle; + uv_req_t* req; + + assert(data); + assert(!didTimeout); + + req = (uv_req_t*) data; + handle = (uv_tty_t*) req->data; + loop = handle->loop; + + UnregisterWait(handle->read_raw_wait); + handle->read_raw_wait = NULL; + + SET_REQ_SUCCESS(req); + POST_COMPLETION_FOR_REQ(loop, req); +} + + +static void uv_tty_queue_read_raw(uv_loop_t* loop, uv_tty_t* handle) { + uv_req_t* req; + BOOL r; + + assert(handle->flags & UV_HANDLE_READING); + assert(!(handle->flags & UV_HANDLE_READ_PENDING)); + + assert(handle->handle && handle->handle != INVALID_HANDLE_VALUE); + + handle->read_line_buffer = uv_null_buf_; + + req = &handle->read_req; + memset(&req->overlapped, 0, sizeof(req->overlapped)); + + r = RegisterWaitForSingleObject(&handle->read_raw_wait, + handle->handle, + uv_tty_post_raw_read, + (void*) req, + INFINITE, + WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE); + if (!r) { + handle->read_raw_wait = NULL; + SET_REQ_ERROR(req, GetLastError()); + uv_insert_pending_req(loop, req); + } + + handle->flags |= UV_HANDLE_READ_PENDING; + handle->reqs_pending++; +} + + +static DWORD CALLBACK uv_tty_line_read_thread(void* data) { + uv_loop_t* loop; + uv_tty_t* handle; + uv_req_t* req; + DWORD bytes, read_bytes; + + assert(data); + + req = (uv_req_t*) data; + handle = (uv_tty_t*) req->data; + loop = handle->loop; + + assert(handle->read_line_buffer.base != NULL); + assert(handle->read_line_buffer.len > 0); + + /* ReadConsole can't handle big buffers. */ + if (handle->read_line_buffer.len < 8192) { + bytes = handle->read_line_buffer.len; + } else { + bytes = 8192; + } + + /* Todo: Unicode */ + if (ReadConsoleA(handle->read_line_handle, + (void*) handle->read_line_buffer.base, + bytes, + &read_bytes, + NULL)) { + SET_REQ_SUCCESS(req); + req->overlapped.InternalHigh = read_bytes; + } else { + SET_REQ_ERROR(req, GetLastError()); + } + + POST_COMPLETION_FOR_REQ(loop, req); + return 0; +} + + +static void uv_tty_queue_read_line(uv_loop_t* loop, uv_tty_t* handle) { + uv_req_t* req; + BOOL r; + + assert(handle->flags & UV_HANDLE_READING); + assert(!(handle->flags & UV_HANDLE_READ_PENDING)); + assert(handle->handle && handle->handle != INVALID_HANDLE_VALUE); + + req = &handle->read_req; + memset(&req->overlapped, 0, sizeof(req->overlapped)); + + handle->read_line_buffer = handle->alloc_cb((uv_handle_t*) handle, 8192); + assert(handle->read_line_buffer.base != NULL); + assert(handle->read_line_buffer.len > 0); + + /* Duplicate the console handle, so if we want to cancel the read, we can */ + /* just close this handle duplicate. */ + if (handle->read_line_handle == NULL) { + HANDLE this_process = GetCurrentProcess(); + r = DuplicateHandle(this_process, + handle->handle, + this_process, + &handle->read_line_handle, + 0, + 0, + DUPLICATE_SAME_ACCESS); + if (!r) { + handle->read_line_handle = NULL; + SET_REQ_ERROR(req, GetLastError()); + uv_insert_pending_req(loop, req); + goto out; + } + } + + r = QueueUserWorkItem(uv_tty_line_read_thread, + (void*) req, + WT_EXECUTELONGFUNCTION); + if (!r) { + SET_REQ_ERROR(req, GetLastError()); + uv_insert_pending_req(loop, req); + } + + out: + handle->flags |= UV_HANDLE_READ_PENDING; + handle->reqs_pending++; +} + + +static void uv_tty_queue_read(uv_loop_t* loop, uv_tty_t* handle) { + if (handle->flags & UV_HANDLE_TTY_RAW) { + uv_tty_queue_read_raw(loop, handle); + } else { + uv_tty_queue_read_line(loop, handle); + } +} + + +static const char* get_vt100_fn_key(DWORD code, char shift, char ctrl, + size_t* len) { +#define VK_CASE(vk, normal_str, shift_str, ctrl_str, shift_ctrl_str) \ + case (vk): \ + if (shift && ctrl) { \ + *len = sizeof shift_ctrl_str; \ + return "\033" shift_ctrl_str; \ + } else if (shift) { \ + *len = sizeof shift_str ; \ + return "\033" shift_str; \ + } else if (ctrl) { \ + *len = sizeof ctrl_str; \ + return "\033" ctrl_str; \ + } else { \ + *len = sizeof normal_str; \ + return "\033" normal_str; \ + } + + switch (code) { + /* These mappings are the same as Cygwin's. Unmodified and alt-modified */ + /* keypad keys comply with linux console, modifiers comply with xterm */ + /* modifier usage. F1..f12 and shift-f1..f10 comply with linux console, */ + /* f6..f12 with and without modifiers comply with rxvt. */ + VK_CASE(VK_INSERT, "[2~", "[2;2~", "[2;5~", "[2;6~") + VK_CASE(VK_END, "[4~", "[4;2~", "[4;5~", "[4;6~") + VK_CASE(VK_DOWN, "[B", "[1;2B", "[1;5B", "[1;6B") + VK_CASE(VK_NEXT, "[6~", "[6;2~", "[6;5~", "[6;6~") + VK_CASE(VK_LEFT, "[D", "[1;2D", "[1;5D", "[1;6D") + VK_CASE(VK_CLEAR, "[G", "[1;2G", "[1;5G", "[1;6G") + VK_CASE(VK_RIGHT, "[C", "[1;2C", "[1;5C", "[1;6C") + VK_CASE(VK_UP, "[A", "[1;2A", "[1;5A", "[1;6A") + VK_CASE(VK_HOME, "[1~", "[1;2~", "[1;5~", "[1;6~") + VK_CASE(VK_PRIOR, "[5~", "[5;2~", "[5;5~", "[5;6~") + VK_CASE(VK_DELETE, "[3~", "[3;2~", "[3;5~", "[3;6~") + VK_CASE(VK_NUMPAD0, "[2~", "[2;2~", "[2;5~", "[2;6~") + VK_CASE(VK_NUMPAD1, "[4~", "[4;2~", "[4;5~", "[4;6~") + VK_CASE(VK_NUMPAD2, "[B", "[1;2B", "[1;5B", "[1;6B") + VK_CASE(VK_NUMPAD3, "[6~", "[6;2~", "[6;5~", "[6;6~") + VK_CASE(VK_NUMPAD4, "[D", "[1;2D", "[1;5D", "[1;6D") + VK_CASE(VK_NUMPAD5, "[G", "[1;2G", "[1;5G", "[1;6G") + VK_CASE(VK_NUMPAD6, "[C", "[1;2C", "[1;5C", "[1;6C") + VK_CASE(VK_NUMPAD7, "[A", "[1;2A", "[1;5A", "[1;6A") + VK_CASE(VK_NUMPAD8, "[1~", "[1;2~", "[1;5~", "[1;6~") + VK_CASE(VK_NUMPAD9, "[5~", "[5;2~", "[5;5~", "[5;6~") + VK_CASE(VK_DECIMAL, "[3~", "[3;2~", "[3;5~", "[3;6~") + VK_CASE(VK_F1, "[[A", "[23~", "[11^", "[23^" ) + VK_CASE(VK_F2, "[[B", "[24~", "[12^", "[24^" ) + VK_CASE(VK_F3, "[[C", "[25~", "[13^", "[25^" ) + VK_CASE(VK_F4, "[[D", "[26~", "[14^", "[26^" ) + VK_CASE(VK_F5, "[[E", "[28~", "[15^", "[28^" ) + VK_CASE(VK_F6, "[17~", "[29~", "[17^", "[29^" ) + VK_CASE(VK_F7, "[18~", "[31~", "[18^", "[31^" ) + VK_CASE(VK_F8, "[19~", "[32~", "[19^", "[32^" ) + VK_CASE(VK_F9, "[20~", "[33~", "[20^", "[33^" ) + VK_CASE(VK_F10, "[21~", "[34~", "[21^", "[34^" ) + VK_CASE(VK_F11, "[23~", "[23$", "[23^", "[23@" ) + VK_CASE(VK_F12, "[24~", "[24$", "[24^", "[24@" ) + + default: + *len = 0; + return NULL; + } +#undef VK_CASE +} + + +void uv_process_tty_read_raw_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* req) { + /* Shortcut for handle->last_input_record.Event.KeyEvent. */ +#define KEV handle->last_input_record.Event.KeyEvent + + DWORD records_left, records_read; + uv_buf_t buf; + off_t buf_used; + + assert(handle->type == UV_TTY); + handle->flags &= ~UV_HANDLE_READ_PENDING; + + if (!(handle->flags & UV_HANDLE_READING) || + !(handle->flags & UV_HANDLE_TTY_RAW)) { + goto out; + } + + if (!REQ_SUCCESS(req)) { + /* An error occurred while waiting for the event. */ + if ((handle->flags & UV_HANDLE_READING)) { + handle->flags &= ~UV_HANDLE_READING; + loop->last_error = GET_REQ_UV_ERROR(req); + handle->read_cb((uv_stream_t*)handle, -1, uv_null_buf_); + } + goto out; + } + + /* Fetch the number of events */ + if (!GetNumberOfConsoleInputEvents(handle->handle, &records_left)) { + handle->flags &= ~UV_HANDLE_READING; + uv_set_sys_error(loop, GetLastError()); + handle->read_cb((uv_stream_t*)handle, -1, uv_null_buf_); + goto out; + } + + /* Windows sends a lot of events that we're not interested in, so buf */ + /* will be allocated on demand, when there's actually something to emit. */ + buf = uv_null_buf_; + buf_used = 0; + + while ((records_left > 0 || handle->last_key_len > 0) && + (handle->flags & UV_HANDLE_READING)) { + if (handle->last_key_len == 0) { + /* Read the next input record */ + if (!ReadConsoleInputW(handle->handle, + &handle->last_input_record, + 1, + &records_read)) { + uv_set_sys_error(loop, GetLastError()); + handle->flags &= ~UV_HANDLE_READING; + handle->read_cb((uv_stream_t*) handle, -1, buf); + goto out; + } + records_left--; + + /* Ignore events that are not keyboard events */ + if (handle->last_input_record.EventType != KEY_EVENT) { + continue; + } + + /* Ignore keyup events, unless the left alt key was held and a valid */ + /* unicode character was emitted. */ + if (!KEV.bKeyDown && !(((KEV.dwControlKeyState & LEFT_ALT_PRESSED) || + KEV.wVirtualKeyCode==VK_MENU) && KEV.uChar.UnicodeChar != 0)) { + continue; + } + + /* Ignore keypresses to numpad number keys if the left alt is held */ + /* because the user is composing a character, or windows simulating */ + /* this. */ + if ((KEV.dwControlKeyState & LEFT_ALT_PRESSED) && + !(KEV.dwControlKeyState & ENHANCED_KEY) && + (KEV.wVirtualKeyCode == VK_INSERT || + KEV.wVirtualKeyCode == VK_END || + KEV.wVirtualKeyCode == VK_DOWN || + KEV.wVirtualKeyCode == VK_NEXT || + KEV.wVirtualKeyCode == VK_LEFT || + KEV.wVirtualKeyCode == VK_CLEAR || + KEV.wVirtualKeyCode == VK_RIGHT || + KEV.wVirtualKeyCode == VK_HOME || + KEV.wVirtualKeyCode == VK_UP || + KEV.wVirtualKeyCode == VK_PRIOR || + KEV.wVirtualKeyCode == VK_NUMPAD0 || + KEV.wVirtualKeyCode == VK_NUMPAD1 || + KEV.wVirtualKeyCode == VK_NUMPAD2 || + KEV.wVirtualKeyCode == VK_NUMPAD3 || + KEV.wVirtualKeyCode == VK_NUMPAD4 || + KEV.wVirtualKeyCode == VK_NUMPAD5 || + KEV.wVirtualKeyCode == VK_NUMPAD6 || + KEV.wVirtualKeyCode == VK_NUMPAD7 || + KEV.wVirtualKeyCode == VK_NUMPAD8 || + KEV.wVirtualKeyCode == VK_NUMPAD9)) { + continue; + } + + if (KEV.uChar.UnicodeChar != 0) { + int prefix_len, char_len; + + /* Character key pressed */ + if (KEV.uChar.UnicodeChar >= 0xD800 && + KEV.uChar.UnicodeChar < 0xDC00) { + /* UTF-16 high surrogate */ + handle->last_utf16_high_surrogate = KEV.uChar.UnicodeChar; + continue; + } + + /* Prefix with \u033 if alt was held, but alt was not used as part */ + /* a compose sequence. */ + if ((KEV.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) + && !(KEV.dwControlKeyState & (LEFT_CTRL_PRESSED | + RIGHT_CTRL_PRESSED)) && KEV.bKeyDown) { + handle->last_key[0] = '\033'; + prefix_len = 1; + } else { + prefix_len = 0; + } + + if (KEV.uChar.UnicodeChar >= 0xDC00 && + KEV.uChar.UnicodeChar < 0xE000) { + /* UTF-16 surrogate pair */ + WCHAR utf16_buffer[2] = { handle->last_utf16_high_surrogate, + KEV.uChar.UnicodeChar}; + char_len = WideCharToMultiByte(CP_UTF8, + 0, + utf16_buffer, + 2, + &handle->last_key[prefix_len], + sizeof handle->last_key, + NULL, + NULL); + } else { + /* Single UTF-16 character */ + char_len = WideCharToMultiByte(CP_UTF8, + 0, + &KEV.uChar.UnicodeChar, + 1, + &handle->last_key[prefix_len], + sizeof handle->last_key, + NULL, + NULL); + } + + /* Whatever happened, the last character wasn't a high surrogate. */ + handle->last_utf16_high_surrogate = 0; + + /* If the utf16 character(s) couldn't be converted something must */ + /* be wrong. */ + if (!char_len) { + uv_set_sys_error(loop, GetLastError()); + handle->flags &= ~UV_HANDLE_READING; + handle->read_cb((uv_stream_t*) handle, -1, buf); + goto out; + } + + handle->last_key_len = (unsigned char) (prefix_len + char_len); + handle->last_key_offset = 0; + continue; + + } else { + /* Function key pressed */ + const char* vt100; + size_t prefix_len, vt100_len; + + vt100 = get_vt100_fn_key(KEV.wVirtualKeyCode, + !!(KEV.dwControlKeyState & SHIFT_PRESSED), + !!(KEV.dwControlKeyState & ( + LEFT_CTRL_PRESSED | + RIGHT_CTRL_PRESSED)), + &vt100_len); + + /* If we were unable to map to a vt100 sequence, just ignore. */ + if (!vt100) { + continue; + } + + /* Prefix with \x033 when the alt key was held. */ + if (KEV.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) { + handle->last_key[0] = '\033'; + prefix_len = 1; + } else { + prefix_len = 0; + } + + /* Copy the vt100 sequence to the handle buffer. */ + assert(prefix_len + vt100_len < sizeof handle->last_key); + memcpy(&handle->last_key[prefix_len], vt100, vt100_len); + + handle->last_key_len = (unsigned char) (prefix_len + vt100_len); + handle->last_key_offset = 0; + continue; + } + } else { + /* Copy any bytes left from the last keypress to the user buffer. */ + if (handle->last_key_offset < handle->last_key_len) { + /* Allocate a buffer if needed */ + if (buf_used == 0) { + buf = handle->alloc_cb((uv_handle_t*) handle, 1024); + } + + buf.base[buf_used++] = handle->last_key[handle->last_key_offset++]; + + /* If the buffer is full, emit it */ + if (buf_used == buf.len) { + handle->read_cb((uv_stream_t*) handle, buf_used, buf); + buf = uv_null_buf_; + buf_used = 0; + } + + continue; + } + + /* Apply dwRepeat from the last input record. */ + if (--KEV.wRepeatCount > 0) { + handle->last_key_offset = 0; + continue; + } + + handle->last_key_len = 0; + continue; + } + } + + /* Send the buffer back to the user */ + if (buf_used > 0) { + handle->read_cb((uv_stream_t*) handle, buf_used, buf); + } + + out: + /* Wait for more input events. */ + if ((handle->flags & UV_HANDLE_READING) && + !(handle->flags & UV_HANDLE_READ_PENDING)) { + uv_tty_queue_read(loop, handle); + } + + DECREASE_PENDING_REQ_COUNT(handle); + +#undef KEV +} + + + +void uv_process_tty_read_line_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* req) { + uv_buf_t buf; + + assert(handle->type == UV_TTY); + + buf = handle->read_line_buffer; + + handle->flags &= ~UV_HANDLE_READ_PENDING; + handle->read_line_buffer = uv_null_buf_; + + if (!REQ_SUCCESS(req)) { + /* Read was not successful */ + if ((handle->flags & UV_HANDLE_READING) && + !(handle->flags & UV_HANDLE_TTY_RAW)) { + /* Real error */ + handle->flags &= ~UV_HANDLE_READING; + loop->last_error = GET_REQ_UV_ERROR(req); + handle->read_cb((uv_stream_t*) handle, -1, buf); + } else { + /* The read was cancelled, or whatever we don't care */ + uv_set_sys_error(loop, WSAEWOULDBLOCK); /* maps to UV_EAGAIN */ + handle->read_cb((uv_stream_t*) handle, 0, buf); + } + + } else { + /* Read successful */ + /* TODO: read unicode, convert to utf-8 */ + DWORD bytes = req->overlapped.InternalHigh; + if (bytes == 0) { + uv_set_sys_error(loop, WSAEWOULDBLOCK); /* maps to UV_EAGAIN */ + } + handle->read_cb((uv_stream_t*) handle, bytes, buf); + } + + /* Wait for more input events. */ + if ((handle->flags & UV_HANDLE_READING) && + !(handle->flags & UV_HANDLE_READ_PENDING)) { + uv_tty_queue_read(loop, handle); + } + + DECREASE_PENDING_REQ_COUNT(handle); +} + + +void uv_process_tty_read_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* req) { + + /* If the read_line_buffer member is zero, it must have been an raw read. */ + /* Otherwise it was a line-buffered read. */ + /* FIXME: This is quite obscure. Use a flag or something. */ + if (handle->read_line_buffer.len == 0) { + uv_process_tty_read_raw_req(loop, handle, req); + } else { + uv_process_tty_read_line_req(loop, handle, req); + } +} + + +int uv_tty_read_start(uv_tty_t* handle, uv_alloc_cb alloc_cb, + uv_read_cb read_cb) { + uv_loop_t* loop = handle->loop; + + handle->flags |= UV_HANDLE_READING; + handle->read_cb = read_cb; + handle->alloc_cb = alloc_cb; + + /* If reading was stopped and then started again, there could stell be a */ + /* read request pending. */ + if (handle->flags & UV_HANDLE_READ_PENDING) { + return 0; + } + + /* Maybe the user stopped reading half-way while processing key events. */ + /* Short-circuit if this could be the case. */ + if (handle->last_key_len > 0) { + SET_REQ_SUCCESS(&handle->read_req); + uv_insert_pending_req(handle->loop, (uv_req_t*) &handle->read_req); + return -1; + } + + uv_tty_queue_read(loop, handle); + + return 0; +} + + +int uv_tty_read_stop(uv_tty_t* handle) { + handle->flags &= ~UV_HANDLE_READING; + + /* Cancel raw read */ + if ((handle->flags & UV_HANDLE_READ_PENDING) && + (handle->flags & UV_HANDLE_TTY_RAW)) { + /* Write some bullshit event to force the console wait to return. */ + INPUT_RECORD record; + DWORD written; + memset(&record, 0, sizeof record); + if (!WriteConsoleInputW(handle->handle, &record, 1, &written)) { + uv_set_sys_error(handle->loop, GetLastError()); + return -1; + } + } + + /* Cancel line-buffered read */ + if (handle->read_line_handle != NULL) { + /* Closing this handle will cancel the ReadConsole operation */ + CloseHandle(handle->read_line_handle); + handle->read_line_handle = NULL; + } + + + return 0; +} + + +static void uv_tty_update_virtual_window(CONSOLE_SCREEN_BUFFER_INFO* info) { + uv_tty_virtual_height = info->srWindow.Bottom - info->srWindow.Top + 1; + uv_tty_virtual_width = info->dwSize.X; - if (r) { - return UV_TTY; + /* Recompute virtual window offset row. */ + if (uv_tty_virtual_offset == -1) { + uv_tty_virtual_offset = info->dwCursorPosition.Y; + } else if (uv_tty_virtual_offset < info->dwCursorPosition.Y - + uv_tty_virtual_height + 1) { + /* If suddenly find the cursor outside of the virtual window, it must */ + /* have somehow scrolled. Update the virtual window offset. */ + uv_tty_virtual_offset = info->dwCursorPosition.Y - + uv_tty_virtual_height + 1; } + if (uv_tty_virtual_offset + uv_tty_virtual_height > info->dwSize.Y) { + uv_tty_virtual_offset = info->dwSize.Y - uv_tty_virtual_height; + } + if (uv_tty_virtual_offset < 0) { + uv_tty_virtual_offset = 0; + } +} + + +static COORD uv_tty_make_real_coord(uv_tty_t* handle, + CONSOLE_SCREEN_BUFFER_INFO* info, int x, unsigned char x_relative, int y, + unsigned char y_relative) { + COORD result; + + uv_tty_update_virtual_window(info); + + /* Adjust y position */ + if (y_relative) { + y = info->dwCursorPosition.Y + y; + } else { + y = uv_tty_virtual_offset + y; + } + /* Clip y to virtual client rectangle */ + if (y < uv_tty_virtual_offset) { + y = uv_tty_virtual_offset; + } else if (y >= uv_tty_virtual_offset + uv_tty_virtual_height) { + y = uv_tty_virtual_offset + uv_tty_virtual_height - 1; + } + + /* Adjust x */ + if (x_relative) { + x = info->dwCursorPosition.X + x; + } + /* Clip x */ + if (x < 0) { + x = 0; + } else if (x >= uv_tty_virtual_width) { + x = uv_tty_virtual_width - 1; + } + + result.X = (unsigned short) x; + result.Y = (unsigned short) y; + return result; +} + + +static int uv_tty_emit_text(uv_tty_t* handle, WCHAR buffer[], DWORD length, + DWORD* error) { + DWORD written; + + if (*error != ERROR_SUCCESS) { + return -1; + } + + if (!WriteConsoleW(handle->handle, + (void*) buffer, + length, + &written, + NULL)) { + *error = GetLastError(); + return -1; + } + + return 0; +} + + +static int uv_tty_move_caret(uv_tty_t* handle, int x, unsigned char x_relative, + int y, unsigned char y_relative, DWORD* error) { + CONSOLE_SCREEN_BUFFER_INFO info; + COORD pos; + + if (*error != ERROR_SUCCESS) { + return -1; + } + + retry: + if (!GetConsoleScreenBufferInfo(handle->handle, &info)) { + *error = GetLastError(); + } + + pos = uv_tty_make_real_coord(handle, &info, x, x_relative, y, y_relative); + + if (!SetConsoleCursorPosition(handle->handle, pos)) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + /* The console may be resized - retry */ + goto retry; + } else { + *error = GetLastError(); + return -1; + } + } + + return 0; +} + + +static int uv_tty_clear(uv_tty_t* handle, int dir, char entire_screen, + DWORD* error) { + unsigned short argc = handle->ansi_csi_argc; + unsigned short* argv = handle->ansi_csi_argv; + + CONSOLE_SCREEN_BUFFER_INFO info; + COORD start, end; + DWORD count, written; + + int x1, x2, y1, y2; + int x1r, x2r, y1r, y2r; + + if (*error != ERROR_SUCCESS) { + return -1; + } + + if (dir == 0) { + /* Clear from current position */ + x1 = 0; + x1r = 1; + } else { + /* Clear from column 0 */ + x1 = 0; + x1r = 0; + } + + if (dir == 1) { + /* Clear to current position */ + x2 = 0; + x2r = 1; + } else { + /* Clear to end of row. We pretend the console is 65536 characters wide, */ + /* uv_tty_make_real_coord will clip it to the actual console width. */ + x2 = 0xffff; + x2r = 0; + } + + if (!entire_screen) { + /* Stay on our own row */ + y1 = y2 = 0; + y1r = y2r = 1; + } else { + /* Apply columns direction to row */ + y1 = x1; + y1r = x1r; + y2 = x2; + y2r = x2r; + } + + retry: + if (!GetConsoleScreenBufferInfo(handle->handle, &info)) { + *error = GetLastError(); + return -1; + } + + start = uv_tty_make_real_coord(handle, &info, x1, x1r, y1, y1r); + end = uv_tty_make_real_coord(handle, &info, x2, x2r, y2, y2r); + count = (end.Y * info.dwSize.X + end.X) - + (start.Y * info.dwSize.X + start.X) + 1; + + if (!(FillConsoleOutputCharacterW(handle->handle, + L'\x20', + count, + start, + &written) && + FillConsoleOutputAttribute(handle->handle, + info.wAttributes, + written, + start, + &written))) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + /* The console may be resized - retry */ + goto retry; + } else { + *error = GetLastError(); + return -1; + } + } + + return 0; +} + + +static int uv_tty_set_style(uv_tty_t* handle, DWORD* error) { + unsigned short argc = handle->ansi_csi_argc; + unsigned short* argv = handle->ansi_csi_argv; + int i; + CONSOLE_SCREEN_BUFFER_INFO info; + + char fg_color = -1, bg_color = -1; + char fg_bright = -1, bg_bright = -1; + + if (argc == 0) { + /* Reset mode */ + fg_color = 7; + bg_color = 0; + fg_bright = 0; + bg_bright = 0; + } + + for (i = 0; i < argc; i++) { + short arg = argv[i]; + + if (arg == 0) { + /* Reset mode */ + fg_color = 7; + bg_color = 0; + fg_bright = 0; + bg_bright = 0; + + } else if (arg == 1) { + /* Bright */ + fg_bright = 1; + + } else if (arg == 21 || arg == 22) { + /* Bright off. */ + fg_bright = 0; + + } else if (arg >= 30 && arg <= 37) { + /* Set foreground color */ + fg_color = arg - 30; + + } else if (arg == 39) { + /* Default text color */ + fg_color = 7; + + } else if (arg >= 40 && arg <= 47) { + /* Set background color */ + bg_color = arg - 40; + + } else if (arg == 49) { + /* Default background color */ + bg_color = 0; + } + } + + if (fg_color == -1 && bg_color == -1 && fg_bright == -1 && + bg_bright == -1) { + /* Nothing changed */ + return 0; + } + + if (!GetConsoleScreenBufferInfo(handle->handle, &info)) { + *error = GetLastError(); + return -1; + } + + if (fg_color != -1) { + info.wAttributes &= ~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + if (fg_color & 1) info.wAttributes |= FOREGROUND_RED; + if (fg_color & 2) info.wAttributes |= FOREGROUND_GREEN; + if (fg_color & 4) info.wAttributes |= FOREGROUND_BLUE; + } + + if (fg_bright != -1) { + if (fg_bright) { + info.wAttributes |= FOREGROUND_INTENSITY; + } else { + info.wAttributes &= ~FOREGROUND_INTENSITY; + } + } + + if (bg_color != -1) { + info.wAttributes &= ~(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); + if (bg_color & 1) info.wAttributes |= BACKGROUND_RED; + if (bg_color & 2) info.wAttributes |= BACKGROUND_GREEN; + if (bg_color & 4) info.wAttributes |= BACKGROUND_BLUE; + } + + if (bg_bright != -1) { + if (bg_bright) { + info.wAttributes |= BACKGROUND_INTENSITY; + } else { + info.wAttributes &= ~BACKGROUND_INTENSITY; + } + } + + if (!SetConsoleTextAttribute(handle->handle, info.wAttributes)) { + *error = GetLastError(); + return -1; + } + + return 0; +} + + +static int uv_tty_write_bufs(uv_tty_t* handle, uv_buf_t bufs[], int bufcnt, + DWORD* error) { + /* We can only write 8k characters at a time. Windows can't handle */ + /* much more characters in a single console write anyway. */ + WCHAR utf16_buf[8192]; + DWORD utf16_buf_used = 0; + int i; + +#define FLUSH_TEXT() \ + do { \ + if (utf16_buf_used > 0) { \ + uv_tty_emit_text(handle, utf16_buf, utf16_buf_used, error); \ + utf16_buf_used = 0; \ + } \ + } while (0) + + /* Cache for fast access */ + unsigned char utf8_bytes_left = handle->utf8_bytes_left; + unsigned int utf8_codepoint = handle->utf8_codepoint; + unsigned char previous_eol = handle->previous_eol; + unsigned char ansi_parser_state = handle->ansi_parser_state; + + /* Store the error here. If we encounter an error, stop trying to do i/o */ + /* but keep parsing the buffer so we leave the parser in a consistent */ + /* state. */ + *error = ERROR_SUCCESS; + + EnterCriticalSection(&uv_tty_output_lock); + + for (i = 0; i < bufcnt; i++) { + uv_buf_t buf = bufs[i]; + unsigned int j; + + for (j = 0; j < buf.len; j++) { + unsigned char c = buf.base[j]; + + /* Run the character through the utf8 decoder We happily accept non */ + /* shortest form encodings and invalid code points - there's no real */ + /* harm that can be done. */ + if (utf8_bytes_left == 0) { + /* Read utf-8 start byte */ + DWORD first_zero_bit; + unsigned char not_c = ~c; +#ifdef _MSC_VER /* msvc */ + if (_BitScanReverse(&first_zero_bit, not_c)) { +#else /* assume gcc */ + if (first_zero_bit = __builtin_clzl(not_c), c != 0) { +#endif + if (first_zero_bit == 7) { + /* Ascii - pass right through */ + utf8_codepoint = (unsigned int) c; + + } else if (first_zero_bit <= 5) { + /* Multibyte sequence */ + utf8_codepoint = (0xff >> (8 - first_zero_bit)) & c; + utf8_bytes_left = (char) (6 - first_zero_bit); + + } else { + /* Invalid continuation */ + utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER; + } + + } else { + /* 0xff -- invalid */ + utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER; + } + + } else if ((c & 0xc0) == 0x80) { + /* Valid continuation of utf-8 multibyte sequence */ + utf8_bytes_left--; + utf8_codepoint <<= 6; + utf8_codepoint |= ((unsigned int) c & 0x3f); + + } else { + /* Start byte where continuation was expected. */ + utf8_bytes_left = 0; + utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER; + /* Patch buf offset so this character will be parsed again as a */ + /* start byte. */ + j--; + } + + /* Maybe we need to parse more bytes to find a character. */ + if (utf8_bytes_left != 0) { + continue; + } + + /* Parse vt100/ansi escape codes */ + if (ansi_parser_state == ANSI_NORMAL) { + switch (utf8_codepoint) { + case '\033': + ansi_parser_state = ANSI_ESCAPE_SEEN; + continue; + + case 0233: + ansi_parser_state = ANSI_CSI; + handle->ansi_csi_argc = 0; + continue; + } + + } else if (ansi_parser_state == ANSI_ESCAPE_SEEN) { + switch (utf8_codepoint) { + case '[': + ansi_parser_state = ANSI_CSI; + handle->ansi_csi_argc = 0; + continue; + + case '^': + case '_': + case 'P': + case ']': + /* Not supported, but we'll have to parse until we see a stop */ + /* code, e.g. ESC \ or BEL. */ + ansi_parser_state = ANSI_ST_CONTROL; + continue; + + case '\033': + /* Ignore double escape. */ + continue; + + default: + if (utf8_codepoint >= '@' && utf8_codepoint <= '_') { + /* Single-char control. */ + ansi_parser_state = ANSI_NORMAL; + continue; + } else { + /* Invalid - proceed as normal, */ + ansi_parser_state = ANSI_NORMAL; + } + } + + } else if (ansi_parser_state & ANSI_CSI) { + if (!(ansi_parser_state & ANSI_IGNORE)) { + if (utf8_codepoint >= '0' && utf8_codepoint <= '9') { + /* Parsing a numerical argument */ + + if (!(ansi_parser_state & ANSI_IN_ARG)) { + /* We were not currently parsing a number */ + + /* Check for too many arguments */ + if (handle->ansi_csi_argc >= COUNTOF(handle->ansi_csi_argv)) { + ansi_parser_state |= ANSI_IGNORE; + continue; + } + + ansi_parser_state |= ANSI_IN_ARG; + handle->ansi_csi_argc++; + handle->ansi_csi_argv[handle->ansi_csi_argc - 1] = + (unsigned short) utf8_codepoint - '0'; + continue; + } else { + /* We were already parsing a number. Parse next digit. */ + uint32_t value = 10 * + handle->ansi_csi_argv[handle->ansi_csi_argc - 1]; + + /* Check for overflow. */ + if (value > UINT16_MAX) { + ansi_parser_state |= ANSI_IGNORE; + continue; + } + + handle->ansi_csi_argv[handle->ansi_csi_argc - 1] = + (unsigned short) value + (utf8_codepoint - '0'); + continue; + } + + } else if (utf8_codepoint == ';') { + /* Denotes the end of an argument. */ + if (ansi_parser_state & ANSI_IN_ARG) { + ansi_parser_state &= ~ANSI_IN_ARG; + continue; + + } else { + /* If ANSI_IN_ARG is not set, add another argument and */ + /* default it to 0. */ + /* Check for too many arguments */ + if (handle->ansi_csi_argc >= COUNTOF(handle->ansi_csi_argv)) { + ansi_parser_state |= ANSI_IGNORE; + continue; + } + + handle->ansi_csi_argc++; + handle->ansi_csi_argv[handle->ansi_csi_argc - 1] = 0; + continue; + } + + } else if (utf8_codepoint >= '@' && utf8_codepoint <= '~' && + (handle->ansi_csi_argc > 0 || utf8_codepoint != '[')) { + int x, y, d; + + /* Command byte */ + switch (utf8_codepoint) { + case 'A': + /* cursor up */ + FLUSH_TEXT(); + y = -(handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1); + uv_tty_move_caret(handle, 0, 1, y, 1, error); + break; + + case 'B': + /* cursor down */ + FLUSH_TEXT(); + y = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1; + uv_tty_move_caret(handle, 0, 1, y, 1, error); + break; + + case 'C': + /* cursor forward */ + FLUSH_TEXT(); + x = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1; + uv_tty_move_caret(handle, x, 1, 0, 1, error); + break; + + case 'D': + /* cursor back */ + FLUSH_TEXT(); + x = -(handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1); + uv_tty_move_caret(handle, x, 1, 0, 1, error); + break; + + case 'E': + /* cursor next line */ + FLUSH_TEXT(); + y = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1; + uv_tty_move_caret(handle, 0, 0, y, 1, error); + break; + + case 'F': + /* cursor previous line */ + FLUSH_TEXT(); + y = -(handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1); + uv_tty_move_caret(handle, 0, 0, y, 1, error); + break; + + case 'G': + /* cursor horizontal move absolute */ + FLUSH_TEXT(); + x = (handle->ansi_csi_argc >= 1 && handle->ansi_csi_argv[0]) + ? handle->ansi_csi_argv[0] - 1 : 0; + uv_tty_move_caret(handle, x, 0, 0, 1, error); + break; + + case 'H': + case 'f': + /* cursor move absolute */ + FLUSH_TEXT(); + y = (handle->ansi_csi_argc >= 1 && handle->ansi_csi_argv[0]) + ? handle->ansi_csi_argv[0] - 1 : 0; + x = (handle->ansi_csi_argc >= 2 && handle->ansi_csi_argv[1]) + ? handle->ansi_csi_argv[1] - 1 : 0; + uv_tty_move_caret(handle, x, 0, y, 0, error); + break; + + case 'J': + /* Erase screen */ + FLUSH_TEXT(); + d = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 0; + if (d >= 0 && d <= 2) { + uv_tty_clear(handle, d, 1, error); + } + break; + + case 'K': + /* Erase line */ + FLUSH_TEXT(); + d = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 0; + if (d >= 0 && d <= 2) { + uv_tty_clear(handle, d, 0, error); + } + break; + + case 'm': + /* Set style */ + FLUSH_TEXT(); + uv_tty_set_style(handle, error); + break; + } + + /* Sequence ended - go back to normal state. */ + ansi_parser_state = ANSI_NORMAL; + continue; + + } else { + /* We don't support commands that use private mode characters or */ + /* intermediaries. Ignore the rest of the sequence. */ + ansi_parser_state |= ANSI_IGNORE; + continue; + } + } else { + /* We're ignoring this command. Stop only on command character. */ + if (utf8_codepoint >= '@' && utf8_codepoint <= '~') { + ansi_parser_state = ANSI_NORMAL; + } + continue; + } + + } else if (ansi_parser_state & ANSI_ST_CONTROL) { + /* Unsupported control code */ + /* Ignore everything until we see BEL or ESC \ */ + if (ansi_parser_state & ANSI_IN_STRING) { + if (!(ansi_parser_state & ANSI_BACKSLASH_SEEN)) { + if (utf8_codepoint == '"') { + ansi_parser_state &= ~ANSI_IN_STRING; + } else if (utf8_codepoint == '\\') { + ansi_parser_state |= ANSI_BACKSLASH_SEEN; + } + } else { + ansi_parser_state &= ~ANSI_BACKSLASH_SEEN; + } + } else { + if (utf8_codepoint == '\007' || (utf8_codepoint == '\\' && + (ansi_parser_state & ANSI_ESCAPE_SEEN))) { + /* End of sequence */ + ansi_parser_state = ANSI_NORMAL; + } else if (utf8_codepoint == '\033') { + /* Escape character */ + ansi_parser_state |= ANSI_ESCAPE_SEEN; + } else if (utf8_codepoint == '"') { + /* String starting */ + ansi_parser_state |= ANSI_IN_STRING; + ansi_parser_state &= ~ANSI_ESCAPE_SEEN; + ansi_parser_state &= ~ANSI_BACKSLASH_SEEN; + } else { + ansi_parser_state &= ~ANSI_ESCAPE_SEEN; + } + } + continue; + } else { + /* Inconsistent state */ + abort(); + } + + /* We wouldn't mind emitting utf-16 surrogate pairs. Too bad, the */ + /* windows console doesn't really support UTF-16, so just emit the */ + /* replacement character. */ + if (utf8_codepoint > 0xffff) { + utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER; + } + + if (utf8_codepoint == 0x0a || utf8_codepoint == 0x0d) { + /* EOL conversion - emit \r\n, when we see either \r or \n. */ + /* If a \n immediately follows a \r or vice versa, ignore it. */ + if (previous_eol == 0 || utf8_codepoint == previous_eol) { + /* If there's no room in the utf16 buf, flush it first. */ + if (2 > COUNTOF(utf16_buf) - utf16_buf_used) { + uv_tty_emit_text(handle, utf16_buf, utf16_buf_used, error); + utf16_buf_used = 0; + } + + utf16_buf[utf16_buf_used++] = L'\r'; + utf16_buf[utf16_buf_used++] = L'\n'; + previous_eol = (char) utf8_codepoint; + } else { + /* Ignore this newline, but don't ignore later ones. */ + previous_eol = 0; + } + + } else if (utf8_codepoint <= 0xffff) { + /* Encode character into utf-16 buffer. */ + + /* If there's no room in the utf16 buf, flush it first. */ + if (1 > COUNTOF(utf16_buf) - utf16_buf_used) { + uv_tty_emit_text(handle, utf16_buf, utf16_buf_used, error); + utf16_buf_used = 0; + } + + utf16_buf[utf16_buf_used++] = (WCHAR) utf8_codepoint; + previous_eol = 0; + } + } + } + + /* Flush remaining characters */ + FLUSH_TEXT(); + + /* Copy cached values back to struct. */ + handle->utf8_bytes_left = utf8_bytes_left; + handle->utf8_codepoint = utf8_codepoint; + handle->previous_eol = previous_eol; + handle->ansi_parser_state = ansi_parser_state; + + LeaveCriticalSection(&uv_tty_output_lock); + + if (*error == STATUS_SUCCESS) { + return 0; + } else { + return -1; + } + +#undef FLUSH_TEXT +} + + +int uv_tty_write(uv_loop_t* loop, uv_write_t* req, uv_tty_t* handle, + uv_buf_t bufs[], int bufcnt, uv_write_cb cb) { + DWORD error; + + if ((handle->flags & UV_HANDLE_SHUTTING) || + (handle->flags & UV_HANDLE_CLOSING)) { + uv_set_sys_error(loop, WSAESHUTDOWN); + return -1; + } + + uv_req_init(loop, (uv_req_t*) req); + req->type = UV_WRITE; + req->handle = (uv_stream_t*) handle; + req->cb = cb; + + handle->reqs_pending++; + handle->write_reqs_pending++; + + req->queued_bytes = 0; + + if (!uv_tty_write_bufs(handle, bufs, bufcnt, &error)) { + SET_REQ_SUCCESS(req); + } else { + SET_REQ_ERROR(req, error); + } + + uv_insert_pending_req(loop, (uv_req_t*) req); + + return 0; +} + + +void uv_process_tty_write_req(uv_loop_t* loop, uv_tty_t* handle, + uv_write_t* req) { + + handle->write_queue_size -= req->queued_bytes; + + if (req->cb) { + loop->last_error = GET_REQ_UV_ERROR(req); + ((uv_write_cb)req->cb)(req, loop->last_error.code == UV_OK ? 0 : -1); + } + + handle->write_reqs_pending--; + if (handle->flags & UV_HANDLE_SHUTTING && + handle->write_reqs_pending == 0) { + uv_want_endgame(loop, (uv_handle_t*)handle); + } + + DECREASE_PENDING_REQ_COUNT(handle); +} + + +void uv_tty_close(uv_tty_t* handle) { + uv_tty_read_stop(handle); + CloseHandle(handle->handle); + + if (handle->reqs_pending == 0) { + uv_want_endgame(handle->loop, (uv_handle_t*) handle); + } +} + + +void uv_tty_endgame(uv_loop_t* loop, uv_tty_t* handle) { + if (handle->flags & UV_HANDLE_CONNECTION && + handle->flags & UV_HANDLE_SHUTTING && + !(handle->flags & UV_HANDLE_SHUT) && + handle->write_reqs_pending == 0) { + handle->flags |= UV_HANDLE_SHUT; + + /* TTY shutdown is really just a no-op */ + if (handle->shutdown_req->cb) { + handle->shutdown_req->cb(handle->shutdown_req, 0); + } + + DECREASE_PENDING_REQ_COUNT(handle); + return; + } + + if (handle->flags & UV_HANDLE_CLOSING && + handle->reqs_pending == 0) { + /* The console handle duplicate used for line reading should be destroyed */ + /* by uv_tty_read_stop. */ + assert(handle->read_line_handle == NULL); + + /* The wait handle used for raw reading should be unregistered when the */ + /* wait callback runs. */ + assert(handle->read_raw_wait == NULL); + + assert(!(handle->flags & UV_HANDLE_CLOSED)); + handle->flags |= UV_HANDLE_CLOSED; + + if (handle->close_cb) { + handle->close_cb((uv_handle_t*)handle); + } + + uv_unref(loop); + } +} + + +/* TODO: remove me */ +void uv_process_tty_accept_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* raw_req) { + abort(); +} - assert(0 && "implement me"); - return UV_UNKNOWN_HANDLE; +/* TODO: remove me */ +void uv_process_tty_connect_req(uv_loop_t* loop, uv_tty_t* handle, + uv_connect_t* req) { + abort(); } diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h index bf376e39141..1d243c48f2d 100644 --- a/deps/uv/test/test-list.h +++ b/deps/uv/test/test-list.h @@ -22,6 +22,8 @@ TEST_DECLARE (tty) TEST_DECLARE (tcp_ping_pong) TEST_DECLARE (tcp_ping_pong_v6) +TEST_DECLARE (tcp_ref) +TEST_DECLARE (tcp_ref2) TEST_DECLARE (pipe_ping_pong) TEST_DECLARE (delayed_accept) TEST_DECLARE (tcp_writealot) @@ -52,6 +54,8 @@ TEST_DECLARE (connection_fail_doesnt_auto_close) TEST_DECLARE (shutdown_eof) TEST_DECLARE (callback_stack) TEST_DECLARE (timer) +TEST_DECLARE (timer_ref) +TEST_DECLARE (timer_ref2) TEST_DECLARE (timer_again) TEST_DECLARE (idle_starvation) TEST_DECLARE (loop_handles) @@ -104,6 +108,12 @@ HELPER_DECLARE (pipe_echo_server) TASK_LIST_START TEST_ENTRY (tty) + + TEST_ENTRY (tcp_ref) + + TEST_ENTRY (tcp_ref2) + TEST_HELPER (tcp_ref2, tcp4_echo_server) + TEST_ENTRY (tcp_ping_pong) TEST_HELPER (tcp_ping_pong, tcp4_echo_server) @@ -154,6 +164,8 @@ TASK_LIST_START TEST_HELPER (callback_stack, tcp4_echo_server) TEST_ENTRY (timer) + TEST_ENTRY (timer_ref) + TEST_ENTRY (timer_ref2) TEST_ENTRY (timer_again) diff --git a/deps/uv/test/test-tcp-close.c b/deps/uv/test/test-tcp-close.c index 5da8a84f8a2..f5dd0a44058 100644 --- a/deps/uv/test/test-tcp-close.c +++ b/deps/uv/test/test-tcp-close.c @@ -127,3 +127,50 @@ TEST_IMPL(tcp_close) { return 0; } + + +TEST_IMPL(tcp_ref) { + uv_tcp_t never; + int r; + + /* A tcp just initialized should count as one reference. */ + r = uv_tcp_init(uv_default_loop(), &never); + ASSERT(r == 0); + + /* One unref should set the loop ref count to zero. */ + uv_unref(uv_default_loop()); + + /* Therefore this does not block */ + uv_run(uv_default_loop()); + + return 0; +} + + +static void never_cb(uv_connect_t* conn_req, int status) { + FATAL("never_cb should never be called"); +} + + +TEST_IMPL(tcp_ref2) { + uv_tcp_t never; + int r; + + /* A tcp just initialized should count as one reference. */ + r = uv_tcp_init(uv_default_loop(), &never); + ASSERT(r == 0); + + r = uv_tcp_connect(&connect_req, + &never, + uv_ip4_addr("127.0.0.1", TEST_PORT), + never_cb); + ASSERT(r == 0); + + /* One unref should set the loop ref count to zero. */ + uv_unref(uv_default_loop()); + + /* Therefore this does not block */ + uv_run(uv_default_loop()); + + return 0; +} diff --git a/deps/uv/test/test-tcp-write-error.c b/deps/uv/test/test-tcp-write-error.c index f3d12b8d6bf..c5907676be9 100644 --- a/deps/uv/test/test-tcp-write-error.c +++ b/deps/uv/test/test-tcp-write-error.c @@ -35,10 +35,15 @@ static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size); static uv_tcp_t tcp_server; static uv_tcp_t tcp_client; static uv_tcp_t tcp_peer; /* client socket as accept()-ed by server */ -static uv_write_t write_req; static uv_connect_t connect_req; static int write_cb_called; +static int write_cb_error_called; + +typedef struct { + uv_write_t req; + uv_buf_t buf; +} write_req_t; static void connection_cb(uv_stream_t* server, int status) { @@ -75,40 +80,47 @@ static void connect_cb(uv_connect_t* req, int status) { size_t size; char* data; int r; + write_req_t* wr; ASSERT(req == &connect_req); ASSERT(status == 0); - size = 10*1024*1024; - data = malloc(size); - ASSERT(data != NULL); - - memset(data, '$', size); - buf = uv_buf_init(data, size); - - write_req.data = data; - - r = uv_write(&write_req, req->handle, &buf, 1, write_cb); - ASSERT(r == 0); - - /* Write queue should have been updated. */ - ASSERT(req->handle->write_queue_size > 0); - - /* write_queue_size <= size, part may have already been written. */ - ASSERT(req->handle->write_queue_size <= size); + while (1) { + size = 10 * 1024 * 1024; + data = malloc(size); + ASSERT(data != NULL); + + memset(data, '$', size); + buf = uv_buf_init(data, size); + + wr = (write_req_t*) malloc(sizeof *wr); + wr->buf = buf; + wr->req.data = data; + + r = uv_write(&(wr->req), req->handle, &wr->buf, 1, write_cb); + ASSERT(r == 0); + + if (req->handle->write_queue_size > 0) { + break; + } + } } static void write_cb(uv_write_t* req, int status) { - ASSERT(req == &write_req); - ASSERT(status == -1); + write_req_t* wr; + wr = (write_req_t*) req; - /* This is what this test is all about. */ - ASSERT(tcp_client.write_queue_size == 0); + if (status == -1) { + write_cb_error_called++; + } - free(write_req.data); + if (req->handle->write_queue_size == 0) { + uv_close((uv_handle_t*)&tcp_client, NULL); + } - uv_close((uv_handle_t*)&tcp_client, NULL); + free(wr->buf.base); + free(wr); write_cb_called++; } @@ -148,7 +160,9 @@ TEST_IMPL(tcp_write_error) { r = uv_run(loop); ASSERT(r == 0); - ASSERT(write_cb_called == 1); + ASSERT(write_cb_called > 0); + ASSERT(write_cb_error_called == 1); + ASSERT(tcp_client.write_queue_size == 0); return 0; } diff --git a/deps/uv/test/test-timer.c b/deps/uv/test/test-timer.c index 17bcb84b770..87235a51bc7 100644 --- a/deps/uv/test/test-timer.c +++ b/deps/uv/test/test-timer.c @@ -130,3 +130,43 @@ TEST_IMPL(timer) { return 0; } + + +TEST_IMPL(timer_ref) { + uv_timer_t never; + int r; + + /* A timer just initialized should count as one reference. */ + r = uv_timer_init(uv_default_loop(), &never); + ASSERT(r == 0); + + /* One unref should set the loop ref count to zero. */ + uv_unref(uv_default_loop()); + + /* Therefore this does not block */ + uv_run(uv_default_loop()); + + return 0; +} + + +TEST_IMPL(timer_ref2) { + uv_timer_t never; + int r; + + /* A timer just initialized should count as one reference. */ + r = uv_timer_init(uv_default_loop(), &never); + ASSERT(r == 0); + + /* We start the timer, this should not effect the ref count. */ + r = uv_timer_start(&never, never_cb, 1000, 1000); + ASSERT(r == 0); + + /* One unref should set the loop ref count to zero. */ + uv_unref(uv_default_loop()); + + /* Therefore this does not block */ + uv_run(uv_default_loop()); + + return 0; +}