Perform a stable sort in order to preserve the order
[TortoiseGit.git] / src / TortoisePlink / Windows / WINPLINK.C
blobc147122d395d2acdb8ef70ea4705c49e3c885cff
1 /*\r
2  * PLink - a Windows command-line (stdin/stdout) variant of PuTTY.\r
3  */\r
4 \r
5 #include <stdio.h>\r
6 #include <stdlib.h>\r
7 #include <assert.h>\r
8 #include <stdarg.h>\r
9 \r
10 #define PUTTY_DO_GLOBALS               /* actually _define_ globals */\r
11 #include "putty.h"\r
12 #include "storage.h"\r
13 #include "tree234.h"\r
15 #define WM_AGENT_CALLBACK (WM_APP + 4)\r
17 struct agent_callback {\r
18     void (*callback)(void *, void *, int);\r
19     void *callback_ctx;\r
20     void *data;\r
21     int len;\r
22 };\r
24 void fatalbox(char *p, ...)\r
25 {\r
26         va_list ap;\r
27         char *stuff, morestuff[100];\r
29         va_start(ap, p);\r
30         stuff = dupvprintf(p, ap);\r
31         va_end(ap);\r
32         sprintf(morestuff, "%.70s Fatal Error", appname);\r
33         MessageBox(GetParentHwnd(), stuff, morestuff, MB_ICONERROR | MB_OK);\r
34         sfree(stuff);\r
35     if (logctx) {\r
36         log_free(logctx);\r
37         logctx = NULL;\r
38     }\r
39         cleanup_exit(1);\r
40 }\r
41 void modalfatalbox(char *p, ...)\r
42 {\r
43         va_list ap;\r
44         char *stuff, morestuff[100];\r
46         va_start(ap, p);\r
47         stuff = dupvprintf(p, ap);\r
48         va_end(ap);\r
49         sprintf(morestuff, "%.70s Fatal Error", appname);\r
50         MessageBox(GetParentHwnd(), stuff, morestuff,\r
51                 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);\r
52         sfree(stuff);\r
53     if (logctx) {\r
54         log_free(logctx);\r
55         logctx = NULL;\r
56     }\r
57     cleanup_exit(1);\r
58 }\r
59 void nonfatal(char *p, ...)\r
60 {\r
61     va_list ap;\r
62     char *stuff, morestuff[100];\r
64     va_start(ap, p);\r
65     stuff = dupvprintf(p, ap);\r
66     va_end(ap);\r
67     sprintf(morestuff, "%.70s Fatal Error", appname);\r
68     MessageBox(GetParentHwnd(), stuff, morestuff,\r
69         MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);\r
70     sfree(stuff);\r
71     if (logctx) {\r
72         log_free(logctx);\r
73         logctx = NULL;\r
74     }\r
75 }\r
76 void connection_fatal(void *frontend, char *p, ...)\r
77 {\r
78     va_list ap;\r
79         char *stuff, morestuff[100];\r
81         va_start(ap, p);\r
82         stuff = dupvprintf(p, ap);\r
83         va_end(ap);\r
84         sprintf(morestuff, "%.70s Fatal Error", appname);\r
85         MessageBox(GetParentHwnd(), stuff, morestuff,\r
86                 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);\r
87         sfree(stuff);\r
88     if (logctx) {\r
89         log_free(logctx);\r
90         logctx = NULL;\r
91     }\r
92         cleanup_exit(1);\r
93 }\r
94 void cmdline_error(char *p, ...)\r
95 {\r
96         va_list ap;\r
97         char *stuff, morestuff[100];\r
99         va_start(ap, p);\r
100         stuff = dupvprintf(p, ap);\r
101         va_end(ap);\r
102         sprintf(morestuff, "%.70s Command Line Error", appname);\r
103         MessageBox(GetParentHwnd(), stuff, morestuff, MB_ICONERROR | MB_OK);\r
104         sfree(stuff);\r
105     exit(1);\r
108 HANDLE inhandle, outhandle, errhandle;\r
109 struct handle *stdin_handle, *stdout_handle, *stderr_handle;\r
110 DWORD orig_console_mode;\r
111 int connopen;\r
113 WSAEVENT netevent;\r
115 static Backend *back;\r
116 static void *backhandle;\r
117 static Conf *conf;\r
119 int term_ldisc(Terminal *term, int mode)\r
121     return FALSE;\r
123 void ldisc_update(void *frontend, int echo, int edit)\r
125     /* Update stdin read mode to reflect changes in line discipline. */\r
126     DWORD mode;\r
128     mode = ENABLE_PROCESSED_INPUT;\r
129     if (echo)\r
130         mode = mode | ENABLE_ECHO_INPUT;\r
131     else\r
132         mode = mode & ~ENABLE_ECHO_INPUT;\r
133     if (edit)\r
134         mode = mode | ENABLE_LINE_INPUT;\r
135     else\r
136         mode = mode & ~ENABLE_LINE_INPUT;\r
137     SetConsoleMode(inhandle, mode);\r
140 char *get_ttymode(void *frontend, const char *mode) { return NULL; }\r
142 int from_backend(void *frontend_handle, int is_stderr,\r
143                  const char *data, int len)\r
145     if (is_stderr) {\r
146         handle_write(stderr_handle, data, len);\r
147     } else {\r
148         handle_write(stdout_handle, data, len);\r
149     }\r
151     return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);\r
154 int from_backend_untrusted(void *frontend_handle, const char *data, int len)\r
156     /*\r
157      * No "untrusted" output should get here (the way the code is\r
158      * currently, it's all diverted by FLAG_STDERR).\r
159      */\r
160     assert(!"Unexpected call to from_backend_untrusted()");\r
161     return 0; /* not reached */\r
164 int from_backend_eof(void *frontend_handle)\r
166     handle_write_eof(stdout_handle);\r
167     return FALSE;   /* do not respond to incoming EOF with outgoing */\r
170 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)\r
172     int ret;\r
173     ret = cmdline_get_passwd_input(p, in, inlen);\r
174     if (ret == -1)\r
175         ret = console_get_userpass_input(p, in, inlen);\r
176     return ret;\r
179 static DWORD main_thread_id;\r
181 void agent_schedule_callback(void (*callback)(void *, void *, int),\r
182                              void *callback_ctx, void *data, int len)\r
184     struct agent_callback *c = snew(struct agent_callback);\r
185     c->callback = callback;\r
186     c->callback_ctx = callback_ctx;\r
187     c->data = data;\r
188     c->len = len;\r
189     PostThreadMessage(main_thread_id, WM_AGENT_CALLBACK, 0, (LPARAM)c);\r
192 /*\r
193  *  Short description of parameters.\r
194  */\r
195 static void usage(void)\r
197         char buf[10000];\r
198         int j = 0;\r
200         j += sprintf(buf+j, "TortoiseGitPlink: command-line connection utility (based on PuTTY Plink)\n");\r
201     j += sprintf(buf+j, "%s\n", ver);\r
202     j += sprintf(buf+j, "Usage: tortoisegitplink [options] [user@]host [command]\n");\r
203     j += sprintf(buf+j, "       (\"host\" can also be a PuTTY saved session name)\n");\r
204     j += sprintf(buf+j, "Options:\n");\r
205     j += sprintf(buf+j, "  -V        print version information and exit\n");\r
206     j += sprintf(buf+j, "  -pgpfp    print PGP key fingerprints and exit\n");\r
207     j += sprintf(buf+j, "  -v        show verbose messages\n");\r
208     j += sprintf(buf+j, "  -load sessname  Load settings from saved session\n");\r
209     j += sprintf(buf+j, "  -ssh -telnet -rlogin -raw\n");\r
210     j += sprintf(buf+j, "            force use of a particular protocol\n");\r
211     j += sprintf(buf+j, "  -P port   connect to specified port\n");\r
212     j += sprintf(buf+j, "  -l user   connect with specified username\n");\r
213     j += sprintf(buf+j, "The following options only apply to SSH connections:\n");\r
214     j += sprintf(buf+j, "  -pw passw login with specified password\n");\r
215     j += sprintf(buf+j, "  -D [listen-IP:]listen-port\n");\r
216     j += sprintf(buf+j, "            Dynamic SOCKS-based port forwarding\n");\r
217     j += sprintf(buf+j, "  -L [listen-IP:]listen-port:host:port\n");\r
218     j += sprintf(buf+j, "            Forward local port to remote address\n");\r
219     j += sprintf(buf+j, "  -R [listen-IP:]listen-port:host:port\n");\r
220     j += sprintf(buf+j, "            Forward remote port to local address\n");\r
221     j += sprintf(buf+j, "  -X -x     enable / disable X11 forwarding\n");\r
222     j += sprintf(buf+j, "  -A -a     enable / disable agent forwarding\n");\r
223     j += sprintf(buf+j, "  -t -T     enable / disable pty allocation\n");\r
224     j += sprintf(buf+j, "  -1 -2     force use of particular protocol version\n");\r
225     j += sprintf(buf+j, "  -4 -6     force use of IPv4 or IPv6\n");\r
226     j += sprintf(buf+j, "  -C        enable compression\n");\r
227     j += sprintf(buf+j, "  -i key    private key file for authentication\n");\r
228     j += sprintf(buf+j, "  -noagent  disable use of Pageant\n");\r
229     j += sprintf(buf+j, "  -agent    enable use of Pageant\n");\r
230     j += sprintf(buf+j, "  -m file   read remote command(s) from file\n");\r
231     j += sprintf(buf+j, "  -s        remote command is an SSH subsystem (SSH-2 only)\n");\r
232     j += sprintf(buf+j, "  -N        don't start a shell/command (SSH-2 only)\n");\r
233     j += sprintf(buf+j, "  -nc host:port\n");\r
234     j += sprintf(buf+j, "            open tunnel in place of session (SSH-2 only)\n");\r
235         MessageBox(NULL, buf, "TortoiseGitPlink", MB_ICONINFORMATION);\r
236         exit(1);\r
239 static void version(void)\r
241         printf("TortoiseGitPlink: %s\n", ver);\r
242         exit(1);\r
245 char *do_select(SOCKET skt, int startup)\r
247     int events;\r
248     if (startup) {\r
249         events = (FD_CONNECT | FD_READ | FD_WRITE |\r
250                   FD_OOB | FD_CLOSE | FD_ACCEPT);\r
251     } else {\r
252         events = 0;\r
253     }\r
254     if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {\r
255         switch (p_WSAGetLastError()) {\r
256           case WSAENETDOWN:\r
257             return "Network is down";\r
258           default:\r
259             return "WSAEventSelect(): unknown error";\r
260         }\r
261     }\r
262     return NULL;\r
265 int stdin_gotdata(struct handle *h, void *data, int len)\r
267     if (len < 0) {\r
268         /*\r
269          * Special case: report read error.\r
270          */\r
271         char buf[4096];\r
272         FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -len, 0,\r
273                       buf, lenof(buf), NULL);\r
274         buf[lenof(buf)-1] = '\0';\r
275         if (buf[strlen(buf)-1] == '\n')\r
276             buf[strlen(buf)-1] = '\0';\r
277         fprintf(stderr, "Unable to read from standard input: %s\n", buf);\r
278         cleanup_exit(0);\r
279     }\r
280     noise_ultralight(len);\r
281     if (connopen && back->connected(backhandle)) {\r
282         if (len > 0) {\r
283             return back->send(backhandle, data, len);\r
284         } else {\r
285             back->special(backhandle, TS_EOF);\r
286             return 0;\r
287         }\r
288     } else\r
289         return 0;\r
292 void stdouterr_sent(struct handle *h, int new_backlog)\r
294     if (new_backlog < 0) {\r
295         /*\r
296          * Special case: report write error.\r
297          */\r
298         char buf[4096];\r
299         FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -new_backlog, 0,\r
300                       buf, lenof(buf), NULL);\r
301         buf[lenof(buf)-1] = '\0';\r
302         if (buf[strlen(buf)-1] == '\n')\r
303             buf[strlen(buf)-1] = '\0';\r
304         fprintf(stderr, "Unable to write to standard %s: %s\n",\r
305                 (h == stdout_handle ? "output" : "error"), buf);\r
306         cleanup_exit(0);\r
307     }\r
308     if (connopen && back->connected(backhandle)) {\r
309         back->unthrottle(backhandle, (handle_backlog(stdout_handle) +\r
310                                       handle_backlog(stderr_handle)));\r
311     }\r
314 int main(int argc, char **argv)\r
316     int sending;\r
317     int portnumber = -1;\r
318     SOCKET *sklist;\r
319     int skcount, sksize;\r
320     int exitcode;\r
321     int errors;\r
322     int got_host = FALSE;\r
323     int use_subsystem = 0;\r
324     unsigned long now, next, then;\r
326     sklist = NULL;\r
327     skcount = sksize = 0;\r
328     /*\r
329      * Initialise port and protocol to sensible defaults. (These\r
330      * will be overridden by more or less anything.)\r
331      */\r
332     default_protocol = PROT_SSH;\r
333     default_port = 22;\r
335     flags = FLAG_STDERR;\r
336     /*\r
337      * Process the command line.\r
338      */\r
339     conf = conf_new();\r
340     do_defaults(NULL, conf);\r
341     loaded_session = FALSE;\r
342     errors = 0;\r
343     conf_set_int(conf, CONF_protocol, default_protocol);\r
344     conf_set_int(conf, CONF_port, default_port);\r
345     conf_set_int(conf, CONF_agentfwd, 0);\r
346     conf_set_int(conf, CONF_x11_forward, 0);\r
347     while (--argc) {\r
348         char *p = *++argv;\r
349         if (*p == '-') {\r
350             int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),\r
351                                             1, conf);\r
352             if (ret == -2) {\r
353                 fprintf(stderr,\r
354                         "tortoisegitplink: option \"%s\" requires an argument\n", p);\r
355                 errors = 1;\r
356             } else if (ret == 2) {\r
357                 --argc, ++argv;\r
358             } else if (ret == 1) {\r
359                 continue;\r
360             } else if (!strcmp(p, "-batch")) {\r
361                         // ignore and do not print an error message\r
362             } else if (!strcmp(p, "-s")) {\r
363                 /* Save status to write to conf later. */\r
364                 use_subsystem = 1;\r
365             } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) {\r
366                 version();\r
367             } else if (!strcmp(p, "--help")) {\r
368                 usage();\r
369             } else if (!strcmp(p, "-pgpfp")) {\r
370                 pgp_fingerprints();\r
371                 exit(1);\r
372             } else {\r
373                 fprintf(stderr, "tortoisegitplink: unknown option \"%s\"\n", p);\r
374                 errors = 1;\r
375             }\r
376         } else if (*p) {\r
377             if (!conf_launchable(conf) || !(got_host || loaded_session)) {\r
378                 char *q = p;\r
379                 /*\r
380                  * If the hostname starts with "telnet:", set the\r
381                  * protocol to Telnet and process the string as a\r
382                  * Telnet URL.\r
383                  */\r
384                 if (!strncmp(q, "telnet:", 7)) {\r
385                     char c;\r
387                     q += 7;\r
388                     if (q[0] == '/' && q[1] == '/')\r
389                         q += 2;\r
390                     conf_set_int(conf, CONF_protocol, PROT_TELNET);\r
391                     p = q;\r
392                     while (*p && *p != ':' && *p != '/')\r
393                         p++;\r
394                     c = *p;\r
395                     if (*p)\r
396                         *p++ = '\0';\r
397                     if (c == ':')\r
398                         conf_set_int(conf, CONF_port, atoi(p));\r
399                     else\r
400                         conf_set_int(conf, CONF_port, -1);\r
401                     conf_set_str(conf, CONF_host, q);\r
402                     got_host = TRUE;\r
403                 } else {\r
404                     char *r, *user, *host;\r
405                     /*\r
406                      * Before we process the [user@]host string, we\r
407                      * first check for the presence of a protocol\r
408                      * prefix (a protocol name followed by ",").\r
409                      */\r
410                     r = strchr(p, ',');\r
411                     if (r) {\r
412                         const Backend *b;\r
413                         *r = '\0';\r
414                         b = backend_from_name(p);\r
415                         if (b) {\r
416                             default_protocol = b->protocol;\r
417                             conf_set_int(conf, CONF_protocol,\r
418                                          default_protocol);\r
419                             portnumber = b->default_port;\r
420                         }\r
421                         p = r + 1;\r
422                     }\r
424                     /*\r
425                      * A nonzero length string followed by an @ is treated\r
426                      * as a username. (We discount an _initial_ @.) The\r
427                      * rest of the string (or the whole string if no @)\r
428                      * is treated as a session name and/or hostname.\r
429                      */\r
430                     r = strrchr(p, '@');\r
431                     if (r == p)\r
432                         p++, r = NULL; /* discount initial @ */\r
433                     if (r) {\r
434                         *r++ = '\0';\r
435                         user = p, host = r;\r
436                     } else {\r
437                         user = NULL, host = p;\r
438                     }\r
440                     /*\r
441                      * Now attempt to load a saved session with the\r
442                      * same name as the hostname.\r
443                      */\r
444                     {\r
445                         Conf *conf2 = conf_new();\r
446                         do_defaults(host, conf2);\r
447                         if (loaded_session || !conf_launchable(conf2)) {\r
448                             /* No settings for this host; use defaults */\r
449                             /* (or session was already loaded with -load) */\r
450                             conf_set_str(conf, CONF_host, host);\r
451                             conf_set_int(conf, CONF_port, default_port);\r
452                             got_host = TRUE;\r
453                         } else {\r
454                             conf_copy_into(conf, conf2);\r
455                             loaded_session = TRUE;\r
456                         }\r
457                         conf_free(conf2);\r
458                     }\r
460                     if (user) {\r
461                         /* Patch in specified username. */\r
462                         conf_set_str(conf, CONF_username, user);\r
463                     }\r
465                 }\r
466             } else {\r
467                 char *command;\r
468                 int cmdlen, cmdsize;\r
469                 cmdlen = cmdsize = 0;\r
470                 command = NULL;\r
472                 while (argc) {\r
473                     while (*p) {\r
474                         if (cmdlen >= cmdsize) {\r
475                             cmdsize = cmdlen + 512;\r
476                             command = sresize(command, cmdsize, char);\r
477                         }\r
478                         command[cmdlen++]=*p++;\r
479                     }\r
480                     if (cmdlen >= cmdsize) {\r
481                         cmdsize = cmdlen + 512;\r
482                         command = sresize(command, cmdsize, char);\r
483                     }\r
484                     command[cmdlen++]=' '; /* always add trailing space */\r
485                     if (--argc) p = *++argv;\r
486                 }\r
487                 if (cmdlen) command[--cmdlen]='\0';\r
488                                        /* change trailing blank to NUL */\r
489                 conf_set_str(conf, CONF_remote_cmd, command);\r
490                 conf_set_str(conf, CONF_remote_cmd2, "");\r
491                 conf_set_int(conf, CONF_nopty, TRUE);  /* command => no tty */\r
493                 break;                 /* done with cmdline */\r
494             }\r
495         }\r
496     }\r
498     if (errors)\r
499         return 1;\r
501     if (!conf_launchable(conf) || !(got_host || loaded_session)) {\r
502         usage();\r
503     }\r
505     /*\r
506      * Muck about with the hostname in various ways.\r
507      */\r
508     {\r
509         char *hostbuf = dupstr(conf_get_str(conf, CONF_host));\r
510         char *host = hostbuf;\r
511         char *p, *q;\r
513         /*\r
514          * Trim leading whitespace.\r
515          */\r
516         host += strspn(host, " \t");\r
518         /*\r
519          * See if host is of the form user@host, and separate out\r
520          * the username if so.\r
521          */\r
522         if (host[0] != '\0') {\r
523             char *atsign = strrchr(host, '@');\r
524             if (atsign) {\r
525                 *atsign = '\0';\r
526                 conf_set_str(conf, CONF_username, host);\r
527                 host = atsign + 1;\r
528             }\r
529         }\r
531         /*\r
532          * Trim off a colon suffix if it's there.\r
533          */\r
534         host[strcspn(host, ":")] = '\0';\r
536         /*\r
537          * Remove any remaining whitespace.\r
538          */\r
539         p = hostbuf;\r
540         q = host;\r
541         while (*q) {\r
542             if (*q != ' ' && *q != '\t')\r
543                 *p++ = *q;\r
544             q++;\r
545         }\r
546         *p = '\0';\r
548         conf_set_str(conf, CONF_host, hostbuf);\r
549         sfree(hostbuf);\r
550     }\r
552     /*\r
553      * Perform command-line overrides on session configuration.\r
554      */\r
555     cmdline_run_saved(conf);\r
557     /*\r
558      * Apply subsystem status.\r
559      */\r
560     if (use_subsystem)\r
561         conf_set_int(conf, CONF_ssh_subsys, TRUE);\r
563     if (!*conf_get_str(conf, CONF_remote_cmd) &&\r
564         !*conf_get_str(conf, CONF_remote_cmd2) &&\r
565         !*conf_get_str(conf, CONF_ssh_nc_host))\r
566         flags |= FLAG_INTERACTIVE;\r
568     /*\r
569      * Select protocol. This is farmed out into a table in a\r
570      * separate file to enable an ssh-free variant.\r
571      */\r
572     back = backend_from_proto(conf_get_int(conf, CONF_protocol));\r
573     if (back == NULL) {\r
574         fprintf(stderr,\r
575                 "Internal fault: Unsupported protocol found\n");\r
576         return 1;\r
577     }\r
579     /*\r
580      * Select port.\r
581      */\r
582     if (portnumber != -1)\r
583         conf_set_int(conf, CONF_port, portnumber);\r
585     sk_init();\r
586     if (p_WSAEventSelect == NULL) {\r
587         fprintf(stderr, "Plink requires WinSock 2\n");\r
588         return 1;\r
589     }\r
591     logctx = log_init(NULL, conf);\r
592     console_provide_logctx(logctx);\r
594     /*\r
595      * Start up the connection.\r
596      */\r
597     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);\r
598     {\r
599         const char *error;\r
600         char *realhost;\r
601         /* nodelay is only useful if stdin is a character device (console) */\r
602         int nodelay = conf_get_int(conf, CONF_tcp_nodelay) &&\r
603             (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);\r
605         error = back->init(NULL, &backhandle, conf,\r
606                            conf_get_str(conf, CONF_host),\r
607                            conf_get_int(conf, CONF_port),\r
608                            &realhost, nodelay,\r
609                            conf_get_int(conf, CONF_tcp_keepalives));\r
610         if (error) {\r
611             fprintf(stderr, "Unable to open connection:\n%s", error);\r
612             return 1;\r
613         }\r
614         back->provide_logctx(backhandle, logctx);\r
615         sfree(realhost);\r
616     }\r
617     connopen = 1;\r
619     inhandle = GetStdHandle(STD_INPUT_HANDLE);\r
620     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);\r
621     errhandle = GetStdHandle(STD_ERROR_HANDLE);\r
623     /*\r
624      * Turn off ECHO and LINE input modes. We don't care if this\r
625      * call fails, because we know we aren't necessarily running in\r
626      * a console.\r
627      */\r
628     GetConsoleMode(inhandle, &orig_console_mode);\r
629     SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);\r
631     /*\r
632      * Pass the output handles to the handle-handling subsystem.\r
633      * (The input one we leave until we're through the\r
634      * authentication process.)\r
635      */\r
636     stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);\r
637     stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);\r
639     main_thread_id = GetCurrentThreadId();\r
641     sending = FALSE;\r
643     now = GETTICKCOUNT();\r
645     while (1) {\r
646         int nhandles;\r
647         HANDLE *handles;        \r
648         int n;\r
649         DWORD ticks;\r
651         if (!sending && back->sendok(backhandle)) {\r
652             stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL,\r
653                                             0);\r
654             sending = TRUE;\r
655         }\r
657         if (run_timers(now, &next)) {\r
658             then = now;\r
659             now = GETTICKCOUNT();\r
660             if (now - then > next - then)\r
661                 ticks = 0;\r
662             else\r
663                 ticks = next - now;\r
664         } else {\r
665             ticks = INFINITE;\r
666         }\r
668         handles = handle_get_events(&nhandles);\r
669         handles = sresize(handles, nhandles+1, HANDLE);\r
670         handles[nhandles] = netevent;\r
671         n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks,\r
672                                       QS_POSTMESSAGE);\r
673         if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {\r
674             handle_got_event(handles[n - WAIT_OBJECT_0]);\r
675         } else if (n == WAIT_OBJECT_0 + nhandles) {\r
676             WSANETWORKEVENTS things;\r
677             SOCKET socket;\r
678             extern SOCKET first_socket(int *), next_socket(int *);\r
679             extern int select_result(WPARAM, LPARAM);\r
680             int i, socketstate;\r
682             /*\r
683              * We must not call select_result() for any socket\r
684              * until we have finished enumerating within the tree.\r
685              * This is because select_result() may close the socket\r
686              * and modify the tree.\r
687              */\r
688             /* Count the active sockets. */\r
689             i = 0;\r
690             for (socket = first_socket(&socketstate);\r
691                  socket != INVALID_SOCKET;\r
692                  socket = next_socket(&socketstate)) i++;\r
694             /* Expand the buffer if necessary. */\r
695             if (i > sksize) {\r
696                 sksize = i + 16;\r
697                 sklist = sresize(sklist, sksize, SOCKET);\r
698             }\r
700             /* Retrieve the sockets into sklist. */\r
701             skcount = 0;\r
702             for (socket = first_socket(&socketstate);\r
703                  socket != INVALID_SOCKET;\r
704                  socket = next_socket(&socketstate)) {\r
705                 sklist[skcount++] = socket;\r
706             }\r
708             /* Now we're done enumerating; go through the list. */\r
709             for (i = 0; i < skcount; i++) {\r
710                 WPARAM wp;\r
711                 socket = sklist[i];\r
712                 wp = (WPARAM) socket;\r
713                 if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {\r
714                     static const struct { int bit, mask; } eventtypes[] = {\r
715                         {FD_CONNECT_BIT, FD_CONNECT},\r
716                         {FD_READ_BIT, FD_READ},\r
717                         {FD_CLOSE_BIT, FD_CLOSE},\r
718                         {FD_OOB_BIT, FD_OOB},\r
719                         {FD_WRITE_BIT, FD_WRITE},\r
720                         {FD_ACCEPT_BIT, FD_ACCEPT},\r
721                     };\r
722                     int e;\r
724                     noise_ultralight(socket);\r
725                     noise_ultralight(things.lNetworkEvents);\r
727                     for (e = 0; e < lenof(eventtypes); e++)\r
728                         if (things.lNetworkEvents & eventtypes[e].mask) {\r
729                             LPARAM lp;\r
730                             int err = things.iErrorCode[eventtypes[e].bit];\r
731                             lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);\r
732                             connopen &= select_result(wp, lp);\r
733                         }\r
734                 }\r
735             }\r
736         } else if (n == WAIT_OBJECT_0 + nhandles + 1) {\r
737             MSG msg;\r
738             while (PeekMessage(&msg, INVALID_HANDLE_VALUE,\r
739                                WM_AGENT_CALLBACK, WM_AGENT_CALLBACK,\r
740                                PM_REMOVE)) {\r
741                 struct agent_callback *c = (struct agent_callback *)msg.lParam;\r
742                 c->callback(c->callback_ctx, c->data, c->len);\r
743                 sfree(c);\r
744             }\r
745         }\r
747         if (n == WAIT_TIMEOUT) {\r
748             now = next;\r
749         } else {\r
750             now = GETTICKCOUNT();\r
751         }\r
753         sfree(handles);\r
755         if (sending)\r
756             handle_unthrottle(stdin_handle, back->sendbuffer(backhandle));\r
758         if ((!connopen || !back->connected(backhandle)) &&\r
759             handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)\r
760             break;                     /* we closed the connection */\r
761     }\r
762     exitcode = back->exitcode(backhandle);\r
763     if (exitcode < 0) {\r
764         fprintf(stderr, "Remote process exit code unavailable\n");\r
765         exitcode = 1;                  /* this is an error condition */\r
766     }\r
767     cleanup_exit(exitcode);\r
768     return 0;                          /* placate compiler warning */\r
771 int WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)\r
773         main(__argc,__argv);\r