diff --git a/Recipe b/Recipe index 457e7b66..7aa0d071 100644 --- a/Recipe +++ b/Recipe @@ -244,7 +244,7 @@ SFTP = sftp int64 logging # Miscellaneous objects appearing in all the network utilities (not # Pageant or PuTTYgen). MISC = timing misc version settings tree234 proxy -WINMISC = MISC winstore winnet cmdline windefs winmisc pproxy wintime +WINMISC = MISC winstore winnet winhandl cmdline windefs winmisc pproxy wintime UXMISC = MISC uxstore uxsel uxnet cmdline uxmisc uxproxy time OSXMISC = MISC uxstore uxsel osxsel uxnet uxmisc uxproxy time MACMISC = MISC macstore macnet mtcpnet otnet macmisc macabout pproxy diff --git a/windows/window.c b/windows/window.c index e997ff06..a21c4edc 100644 --- a/windows/window.c +++ b/windows/window.c @@ -816,8 +816,29 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) term_set_focus(term, GetForegroundWindow() == hwnd); UpdateWindow(hwnd); - if (GetMessage(&msg, NULL, 0, 0) == 1) { - while (msg.message != WM_QUIT) { + while (1) { + HANDLE *handles; + int nhandles, n; + + handles = handle_get_events(&nhandles); + + n = MsgWaitForMultipleObjects(nhandles, handles, FALSE, INFINITE, + QS_ALLINPUT); + + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[n - WAIT_OBJECT_0]); + sfree(handles); + continue; + } + + sfree(handles); + + if (GetMessage(&msg, NULL, 0, 0) != 1) + break; + do { + if (msg.message == WM_QUIT) + goto finished; /* two-level break */ + if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg))) DispatchMessage(&msg); /* Send the paste buffer if there's anything to send */ @@ -826,23 +847,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * we've delayed, reading the socket, writing, and repainting * the window. */ - if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - continue; + } while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)); - /* The messages seem unreliable; especially if we're being tricky */ - term_set_focus(term, GetForegroundWindow() == hwnd); + /* The messages seem unreliable; especially if we're being tricky */ + term_set_focus(term, GetForegroundWindow() == hwnd); - net_pending_errors(); - - /* There's no point rescanning everything in the message queue - * so we do an apparently unnecessary wait here - */ - WaitMessage(); - if (GetMessage(&msg, NULL, 0, 0) != 1) - break; - } + net_pending_errors(); } + finished: cleanup_exit(msg.wParam); /* this doesn't return... */ return msg.wParam; /* ... but optimiser doesn't know */ } diff --git a/windows/winhandl.c b/windows/winhandl.c new file mode 100644 index 00000000..305f9971 --- /dev/null +++ b/windows/winhandl.c @@ -0,0 +1,486 @@ +/* + * winhandl.c: Module to give Windows front ends the general + * ability to deal with consoles, pipes, serial ports, or any other + * type of data stream accessed through a Windows API HANDLE rather + * than a WinSock SOCKET. + * + * We do this by spawning a subthread to continuously try to read + * from the handle. Every time a read successfully returns some + * data, the subthread sets an event object which is picked up by + * the main thread, and the main thread then sets an event in + * return to instruct the subthread to resume reading. + * + * Output works precisely the other way round, in a second + * subthread. The output subthread should not be attempting to + * write all the time, because it hasn't always got data _to_ + * write; so the output thread waits for an event object notifying + * it to _attempt_ a write, and then it sets an event in return + * when one completes. + */ + +/* + * TODO: + * + * - could do with some sort of private-data field in each handle + * structure. + */ + +#include + +#include "putty.h" + +/* ---------------------------------------------------------------------- + * Generic definitions. + */ + +/* + * Maximum amount of backlog we will allow to build up on an input + * handle before we stop reading from it. + */ +#define MAX_BACKLOG 32768 + +struct handle_generic { + /* + * Initial fields common to both handle_input and handle_output + * structures. + * + * The three HANDLEs are set up at initialisation time and are + * thereafter read-only to both main thread and subthread. + * `moribund' is only used by the main thread; `done' is + * written by the main thread before signalling to the + * subthread. `defunct' and `busy' are used only by the main + * thread. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ +}; + +/* ---------------------------------------------------------------------- + * Input threads. + */ + +/* + * Data required by an input thread. + */ +struct handle_input { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + + /* + * Data set by the input thread before signalling ev_to_main, + * and read by the main thread after receiving that signal. + */ + char buffer[4096]; /* the data read from the handle */ + DWORD len; /* how much data that was */ + int readret; /* lets us know about read errors */ + + /* + * Callback function called by this module when data arrives on + * an input handle. + */ + handle_inputfn_t gotdata; +}; + +/* + * The actual thread procedure for an input thread. + */ +static DWORD WINAPI handle_input_threadfunc(void *param) +{ + struct handle_input *ctx = (struct handle_input *) param; + + while (1) { + ctx->readret = ReadFile(ctx->h, ctx->buffer, sizeof(ctx->buffer), + &ctx->len, NULL); + if (!ctx->readret) + ctx->len = 0; + + SetEvent(ctx->ev_to_main); + + if (!ctx->len) + break; + + WaitForSingleObject(ctx->ev_from_main, INFINITE); + if (ctx->done) + break; /* main thread told us to shut down */ + } + + return 0; +} + +/* + * This is called after a succcessful read, or from the + * `unthrottle' function. It decides whether or not to begin a new + * read operation. + */ +static void handle_throttle(struct handle_input *ctx, int backlog) +{ + assert(!ctx->defunct); + + /* + * If there's a read operation already in progress, do nothing: + * when that completes, we'll come back here and be in a + * position to make a better decision. + */ + if (ctx->busy) + return; + + /* + * Otherwise, we must decide whether to start a new read based + * on the size of the backlog. + */ + if (backlog < MAX_BACKLOG) { + SetEvent(ctx->ev_from_main); + ctx->busy = TRUE; + } +} + +/* ---------------------------------------------------------------------- + * Output threads. + */ + +/* + * Data required by an output thread. + */ +struct handle_output { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + + /* + * Data set by the main thread before signalling ev_from_main, + * and read by the input thread after receiving that signal. + */ + char *buffer; /* the data to write */ + DWORD len; /* how much data there is */ + + /* + * Data set by the input thread before signalling ev_to_main, + * and read by the main thread after receiving that signal. + */ + DWORD lenwritten; /* how much data we actually wrote */ + int writeret; /* return value from WriteFile */ + + /* + * Data only ever read or written by the main thread. + */ + bufchain queued_data; /* data still waiting to be written */ + + /* + * Callback function called when the backlog in the bufchain + * drops. + */ + handle_outputfn_t sentdata; +}; + +static DWORD WINAPI handle_output_threadfunc(void *param) +{ + struct handle_output *ctx = (struct handle_output *) param; + + while (1) { + WaitForSingleObject(ctx->ev_from_main, INFINITE); + if (ctx->done) { + SetEvent(ctx->ev_to_main); + break; + } + ctx->writeret = WriteFile(ctx->h, ctx->buffer, ctx->len, + &ctx->lenwritten, NULL); + SetEvent(ctx->ev_to_main); + if (!ctx->writeret) + break; + } + + return 0; +} + +static void handle_try_output(struct handle_output *ctx) +{ + void *senddata; + int sendlen; + + if (!ctx->busy && bufchain_size(&ctx->queued_data)) { + bufchain_prefix(&ctx->queued_data, &senddata, &sendlen); + ctx->buffer = senddata; + ctx->len = sendlen; + SetEvent(ctx->ev_from_main); + ctx->busy = TRUE; + } +} + +/* ---------------------------------------------------------------------- + * Unified code handling both input and output threads. + */ + +struct handle { + int output; + union { + struct handle_generic g; + struct handle_input i; + struct handle_output o; + } u; +}; + +static tree234 *handles_by_evtomain; + +static int handle_cmp_evtomain(void *av, void *bv) +{ + struct handle *a = (struct handle *)av; + struct handle *b = (struct handle *)bv; + + if ((unsigned)a->u.g.ev_to_main < (unsigned)b->u.g.ev_to_main) + return -1; + else if ((unsigned)a->u.g.ev_to_main > (unsigned)b->u.g.ev_to_main) + return +1; + else + return 0; +} + +static int handle_find_evtomain(void *av, void *bv) +{ + HANDLE *a = (HANDLE *)av; + struct handle *b = (struct handle *)bv; + + if ((unsigned)*a < (unsigned)b->u.g.ev_to_main) + return -1; + else if ((unsigned)*a > (unsigned)b->u.g.ev_to_main) + return +1; + else + return 0; +} + +struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata) +{ + struct handle *h = snew(struct handle); + + h->output = FALSE; + h->u.i.h = handle; + h->u.i.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.i.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.i.gotdata = gotdata; + h->u.i.busy = FALSE; + h->u.i.defunct = FALSE; + h->u.i.moribund = FALSE; + h->u.i.done = FALSE; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + CreateThread(NULL, 0, handle_input_threadfunc, + &h->u.i, 0, NULL); + + handle_throttle(&h->u.i, 0); /* start first read operation */ + + return h; +} + +struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata) +{ + struct handle *h = snew(struct handle); + + h->output = TRUE; + h->u.o.h = handle; + h->u.o.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.o.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.o.busy = FALSE; + h->u.o.defunct = FALSE; + h->u.o.moribund = FALSE; + h->u.o.done = FALSE; + bufchain_init(&h->u.o.queued_data); + h->u.o.sentdata = sentdata; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + CreateThread(NULL, 0, handle_output_threadfunc, + &h->u.i, 0, NULL); + + return h; +} + +int handle_write(struct handle *h, const void *data, int len) +{ + assert(h->output); + bufchain_add(&h->u.o.queued_data, data, len); + handle_try_output(&h->u.o); + return bufchain_size(&h->u.o.queued_data); +} + +HANDLE *handle_get_events(int *nevents) +{ + HANDLE *ret; + struct handle *h; + int i, n, size; + + /* + * Go through our tree counting the handle objects currently + * engaged in useful activity. + */ + ret = NULL; + n = size = 0; + if (handles_by_evtomain) { + for (i = 0; (h = index234(handles_by_evtomain, i)) != NULL; i++) { + if (h->u.g.busy) { + if (n >= size) { + size += 32; + ret = sresize(ret, size, HANDLE); + } + ret[n++] = h->u.g.ev_to_main; + } + } + } + + *nevents = n; + return ret; +} + +static void handle_destroy(struct handle *h) +{ + if (h->output) + bufchain_clear(&h->u.o.queued_data); + CloseHandle(h->u.g.ev_from_main); + CloseHandle(h->u.g.ev_to_main); + del234(handles_by_evtomain, h); + sfree(h); +} + +void handle_free(struct handle *h) +{ + /* + * If the handle is currently busy, we cannot immediately free + * it. Instead we must wait until it's finished its current + * operation, because otherwise the subthread will write to + * invalid memory after we free its context from under it. + */ + assert(h && !h->u.g.moribund); + if (h->u.g.busy) { + /* + * Just set the moribund flag, which will be noticed next + * time an operation completes. + */ + h->u.g.moribund = TRUE; + } else if (h->u.g.defunct) { + /* + * There isn't even a subthread; we can go straight to + * handle_destroy. + */ + handle_destroy(h); + } else { + /* + * The subthread is alive but not busy, so we now signal it + * to die. Set the moribund flag to indicate that it will + * want destroying after that. + */ + h->u.g.moribund = TRUE; + h->u.g.done = TRUE; + SetEvent(h->u.g.ev_from_main); + } +} + +void handle_got_event(HANDLE event) +{ + struct handle *h; + + assert(handles_by_evtomain); + h = find234(handles_by_evtomain, &event, handle_find_evtomain); + if (!h) { + /* + * This isn't an error condition. If two or more event + * objects were signalled during the same select operation, + * and processing of the first caused the second handle to + * be closed, then it will sometimes happen that we receive + * an event notification here for a handle which is already + * deceased. In that situation we simply do nothing. + */ + return; + } + + if (h->u.g.moribund) { + /* + * A moribund handle is already treated as dead from the + * external user's point of view, so do nothing with the + * actual event. Just signal the thread to die if + * necessary, or destroy the handle if not. + */ + if (h->u.g.done) { + handle_destroy(h); + } else { + h->u.g.done = TRUE; + SetEvent(h->u.g.ev_from_main); + } + return; + } + + if (!h->output) { + int backlog; + + h->u.i.busy = FALSE; + + /* + * A signal on an input handle means data has arrived. + */ + if (h->u.i.len == 0) { + /* + * EOF, or (nearly equivalently) read error. + */ + h->u.i.gotdata(h, NULL, (h->u.i.readret ? 0 : -1)); + h->u.i.defunct = TRUE; + } else { + backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len); + handle_throttle(&h->u.i, backlog); + } + } else { + h->u.o.busy = FALSE; + + /* + * A signal on an output handle means we have completed a + * write. Call the callback to indicate that the output + * buffer size has decreased, or to indicate an error. + */ + if (!h->u.o.writeret) { + /* + * Write error. Send a negative value to the callback, + * and mark the thread as defunct (because the output + * thread is terminating by now). + */ + h->u.o.sentdata(h, -1); + h->u.o.defunct = TRUE; + } else { + bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); + h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data)); + handle_try_output(&h->u.o); + } + } +} + +void handle_unthrottle(struct handle *h, int backlog) +{ + assert(!h->output); + handle_throttle(&h->u.i, backlog); +} + +int handle_backlog(struct handle *h) +{ + assert(h->output); + return bufchain_size(&h->u.o.queued_data); +} diff --git a/windows/winplink.c b/windows/winplink.c index 6e520c72..4ef2c980 100644 --- a/windows/winplink.c +++ b/windows/winplink.c @@ -14,8 +14,6 @@ #define WM_AGENT_CALLBACK (WM_APP + 4) -#define MAX_STDIN_BACKLOG 4096 - struct agent_callback { void (*callback)(void *, void *, int); void *callback_ctx; @@ -65,7 +63,9 @@ void cmdline_error(char *p, ...) } HANDLE inhandle, outhandle, errhandle; +struct handle *stdin_handle, *stdout_handle, *stderr_handle; DWORD orig_console_mode; +int connopen; WSAEVENT netevent; @@ -96,97 +96,16 @@ void ldisc_update(void *frontend, int echo, int edit) char *get_ttymode(void *frontend, const char *mode) { return NULL; } -struct input_data { - DWORD len; - char buffer[4096]; - HANDLE event, eventback; -}; - -static DWORD WINAPI stdin_read_thread(void *param) -{ - struct input_data *idata = (struct input_data *) param; - HANDLE inhandle; - - inhandle = GetStdHandle(STD_INPUT_HANDLE); - - while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer), - &idata->len, NULL) && idata->len > 0) { - SetEvent(idata->event); - WaitForSingleObject(idata->eventback, INFINITE); - } - - idata->len = 0; - SetEvent(idata->event); - - return 0; -} - -struct output_data { - DWORD len, lenwritten; - int writeret; - char *buffer; - int is_stderr, done; - HANDLE event, eventback; - int busy; -}; - -static DWORD WINAPI stdout_write_thread(void *param) -{ - struct output_data *odata = (struct output_data *) param; - HANDLE outhandle, errhandle; - - outhandle = GetStdHandle(STD_OUTPUT_HANDLE); - errhandle = GetStdHandle(STD_ERROR_HANDLE); - - while (1) { - WaitForSingleObject(odata->eventback, INFINITE); - if (odata->done) - break; - odata->writeret = - WriteFile(odata->is_stderr ? errhandle : outhandle, - odata->buffer, odata->len, &odata->lenwritten, NULL); - SetEvent(odata->event); - } - - return 0; -} - -bufchain stdout_data, stderr_data; -struct output_data odata, edata; - -void try_output(int is_stderr) -{ - struct output_data *data = (is_stderr ? &edata : &odata); - void *senddata; - int sendlen; - - if (!data->busy) { - bufchain_prefix(is_stderr ? &stderr_data : &stdout_data, - &senddata, &sendlen); - data->buffer = senddata; - data->len = sendlen; - SetEvent(data->eventback); - data->busy = 1; - } -} - int from_backend(void *frontend_handle, int is_stderr, const char *data, int len) { - int osize, esize; - if (is_stderr) { - bufchain_add(&stderr_data, data, len); - try_output(1); + handle_write(stderr_handle, data, len); } else { - bufchain_add(&stdout_data, data, len); - try_output(0); + handle_write(stdout_handle, data, len); } - osize = bufchain_size(&stdout_data); - esize = bufchain_size(&stderr_data); - - return osize + esize; + return handle_backlog(stdout_handle) + handle_backlog(stderr_handle); } int from_backend_untrusted(void *frontend_handle, const char *data, int len) @@ -289,18 +208,49 @@ char *do_select(SOCKET skt, int startup) return NULL; } +int stdin_gotdata(struct handle *h, void *data, int len) +{ + if (len < 0) { + /* + * Special case: report read error. + */ + fprintf(stderr, "Unable to read from standard input\n"); + cleanup_exit(0); + } + noise_ultralight(len); + if (connopen && back->socket(backhandle) != NULL) { + if (len > 0) { + return back->send(backhandle, data, len); + } else { + back->special(backhandle, TS_EOF); + return 0; + } + } else + return 0; +} + +void stdouterr_sent(struct handle *h, int new_backlog) +{ + if (new_backlog < 0) { + /* + * Special case: report write error. + */ + fprintf(stderr, "Unable to write to standard %s\n", + (h == stdout_handle ? "output" : "error")); + cleanup_exit(0); + } + if (connopen && back->socket(backhandle) != NULL) { + back->unthrottle(backhandle, (handle_backlog(stdout_handle) + + handle_backlog(stderr_handle))); + } +} + int main(int argc, char **argv) { - WSAEVENT stdinevent, stdoutevent, stderrevent; - HANDLE handles[4]; - DWORD in_threadid, out_threadid, err_threadid; - struct input_data idata; - int reading = FALSE; int sending; int portnumber = -1; SOCKET *sklist; int skcount, sksize; - int connopen; int exitcode; int errors; int use_subsystem = 0; @@ -610,13 +560,10 @@ int main(int argc, char **argv) } connopen = 1; - stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL); - stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL); - stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL); - inhandle = GetStdHandle(STD_INPUT_HANDLE); outhandle = GetStdHandle(STD_OUTPUT_HANDLE); errhandle = GetStdHandle(STD_ERROR_HANDLE); + /* * Turn off ECHO and LINE input modes. We don't care if this * call fails, because we know we aren't necessarily running in @@ -625,69 +572,29 @@ int main(int argc, char **argv) GetConsoleMode(inhandle, &orig_console_mode); SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT); + /* + * Pass the output handles to the handle-handling subsystem. + * (The input one we leave until we're through the + * authentication process.) + */ + stdout_handle = handle_output_new(outhandle, stdouterr_sent); + stderr_handle = handle_output_new(errhandle, stdouterr_sent); + main_thread_id = GetCurrentThreadId(); - handles[0] = netevent; - handles[1] = stdinevent; - handles[2] = stdoutevent; - handles[3] = stderrevent; sending = FALSE; - /* - * Create spare threads to write to stdout and stderr, so we - * can arrange asynchronous writes. - */ - odata.event = stdoutevent; - odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL); - odata.is_stderr = 0; - odata.busy = odata.done = 0; - if (!CreateThread(NULL, 0, stdout_write_thread, - &odata, 0, &out_threadid)) { - fprintf(stderr, "Unable to create output thread\n"); - cleanup_exit(1); - } - edata.event = stderrevent; - edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL); - edata.is_stderr = 1; - edata.busy = edata.done = 0; - if (!CreateThread(NULL, 0, stdout_write_thread, - &edata, 0, &err_threadid)) { - fprintf(stderr, "Unable to create error output thread\n"); - cleanup_exit(1); - } - now = GETTICKCOUNT(); while (1) { + int nhandles; + HANDLE *handles; int n; DWORD ticks; if (!sending && back->sendok(backhandle)) { - /* - * Create a separate thread to read from stdin. This is - * a total pain, but I can't find another way to do it: - * - * - an overlapped ReadFile or ReadFileEx just doesn't - * happen; we get failure from ReadFileEx, and - * ReadFile blocks despite being given an OVERLAPPED - * structure. Perhaps we can't do overlapped reads - * on consoles. WHY THE HELL NOT? - * - * - WaitForMultipleObjects(netevent, console) doesn't - * work, because it signals the console when - * _anything_ happens, including mouse motions and - * other things that don't cause data to be readable - * - so we're back to ReadFile blocking. - */ - idata.event = stdinevent; - idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL); - if (!CreateThread(NULL, 0, stdin_read_thread, - &idata, 0, &in_threadid)) { - fprintf(stderr, "Unable to create input thread\n"); - cleanup_exit(1); - } + stdin_handle = handle_input_new(inhandle, stdin_gotdata); sending = TRUE; - reading = TRUE; } if (run_timers(now, &next)) { @@ -697,9 +604,14 @@ int main(int argc, char **argv) ticks = INFINITE; } - n = MsgWaitForMultipleObjects(4, handles, FALSE, ticks, + handles = handle_get_events(&nhandles); + handles = sresize(handles, nhandles+1, HANDLE); + handles[nhandles] = netevent; + n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks, QS_POSTMESSAGE); - if (n == WAIT_OBJECT_0 + 0) { + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[n - WAIT_OBJECT_0]); + } else if (n == WAIT_OBJECT_0 + nhandles) { WSANETWORKEVENTS things; SOCKET socket; extern SOCKET first_socket(int *), next_socket(int *); @@ -760,43 +672,7 @@ int main(int argc, char **argv) } } } - } else if (n == WAIT_OBJECT_0 + 1) { - reading = 0; - noise_ultralight(idata.len); - if (connopen && back->socket(backhandle) != NULL) { - if (idata.len > 0) { - back->send(backhandle, idata.buffer, idata.len); - } else { - back->special(backhandle, TS_EOF); - } - } - } else if (n == WAIT_OBJECT_0 + 2) { - odata.busy = 0; - if (!odata.writeret) { - fprintf(stderr, "Unable to write to standard output\n"); - cleanup_exit(0); - } - bufchain_consume(&stdout_data, odata.lenwritten); - if (bufchain_size(&stdout_data) > 0) - try_output(0); - if (connopen && back->socket(backhandle) != NULL) { - back->unthrottle(backhandle, bufchain_size(&stdout_data) + - bufchain_size(&stderr_data)); - } - } else if (n == WAIT_OBJECT_0 + 3) { - edata.busy = 0; - if (!edata.writeret) { - fprintf(stderr, "Unable to write to standard output\n"); - cleanup_exit(0); - } - bufchain_consume(&stderr_data, edata.lenwritten); - if (bufchain_size(&stderr_data) > 0) - try_output(1); - if (connopen && back->socket(backhandle) != NULL) { - back->unthrottle(backhandle, bufchain_size(&stdout_data) + - bufchain_size(&stderr_data)); - } - } else if (n == WAIT_OBJECT_0 + 4) { + } else if (n == WAIT_OBJECT_0 + nhandles + 1) { MSG msg; while (PeekMessage(&msg, INVALID_HANDLE_VALUE, WM_AGENT_CALLBACK, WM_AGENT_CALLBACK, @@ -813,13 +689,13 @@ int main(int argc, char **argv) now = GETTICKCOUNT(); } - if (!reading && back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) { - SetEvent(idata.eventback); - reading = 1; - } + sfree(handles); + + if (sending) + handle_unthrottle(stdin_handle, back->sendbuffer(backhandle)); + if ((!connopen || back->socket(backhandle) == NULL) && - bufchain_size(&stdout_data) == 0 && - bufchain_size(&stderr_data) == 0) + handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0) break; /* we closed the connection */ } exitcode = back->exitcode(backhandle); diff --git a/windows/winsftp.c b/windows/winsftp.c index d14e817e..21188978 100644 --- a/windows/winsftp.c +++ b/windows/winsftp.c @@ -463,9 +463,9 @@ extern int select_result(WPARAM, LPARAM); int do_eventsel_loop(HANDLE other_event) { - int n; + int n, nhandles, nallhandles; long next, ticks; - HANDLE handles[2]; + HANDLE *handles; SOCKET *sklist; int skcount; long now = GETTICKCOUNT(); @@ -474,8 +474,13 @@ int do_eventsel_loop(HANDLE other_event) return -1; /* doom */ } - handles[0] = netevent; - handles[1] = other_event; + handles = handle_get_events(&nhandles); + handles = sresize(handles, nhandles+2, HANDLE); + nallhandles = nhandles; + + handles[nallhandles++] = netevent; + if (other_event) + handles[nallhandles++] = other_event; if (run_timers(now, &next)) { ticks = next - GETTICKCOUNT(); @@ -484,10 +489,12 @@ int do_eventsel_loop(HANDLE other_event) ticks = INFINITE; } - n = MsgWaitForMultipleObjects(other_event ? 2 : 1, handles, FALSE, ticks, + n = MsgWaitForMultipleObjects(nallhandles, handles, FALSE, ticks, QS_POSTMESSAGE); - if (n == WAIT_OBJECT_0 + 0) { + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[n - WAIT_OBJECT_0]); + } else if (n == WAIT_OBJECT_0 + nhandles) { WSANETWORKEVENTS things; SOCKET socket; extern SOCKET first_socket(int *), next_socket(int *); @@ -549,13 +556,15 @@ int do_eventsel_loop(HANDLE other_event) sfree(sklist); } + sfree(handles); + if (n == WAIT_TIMEOUT) { now = next; } else { now = GETTICKCOUNT(); } - if (other_event && n == WAIT_OBJECT_0 + 1) + if (other_event && n == WAIT_OBJECT_0 + nhandles + 1) return 1; return 0; diff --git a/windows/winstuff.h b/windows/winstuff.h index 9ec33de6..5f974dc3 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -405,6 +405,21 @@ void EnableSizeTip(int bEnable); struct unicode_data; void init_ucs(Config *, struct unicode_data *); +/* + * Exports from winhandl.c. + */ +struct handle; +typedef int (*handle_inputfn_t)(struct handle *h, void *data, int len); +typedef void (*handle_outputfn_t)(struct handle *h, int new_backlog); +struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata); +struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata); +int handle_write(struct handle *h, const void *data, int len); +HANDLE *handle_get_events(int *nevents); +void handle_free(struct handle *h); +void handle_got_event(HANDLE event); +void handle_unthrottle(struct handle *h, int backlog); +int handle_backlog(struct handle *h); + /* * pageantc.c needs to schedule callbacks for asynchronous agent * requests. This has to be done differently in GUI and console, so