OSDN Git Service

Add test code for SFTP (still useless).
[ffftp/ffftp.git] / putty / WINDOWS / WINSFTP.C
1 /*\r
2  * winsftp.c: the Windows-specific parts of PSFTP and PSCP.\r
3  */\r
4 \r
5 #include <assert.h>\r
6 \r
7 #include "putty.h"\r
8 #include "psftp.h"\r
9 #include "ssh.h"\r
10 #include "int64.h"\r
11 \r
12 char *get_ttymode(void *frontend, const char *mode) { return NULL; }\r
13 \r
14 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)\r
15 {\r
16     int ret;\r
17     ret = cmdline_get_passwd_input(p, in, inlen);\r
18     if (ret == -1)\r
19         ret = console_get_userpass_input(p, in, inlen);\r
20     return ret;\r
21 }\r
22 \r
23 void platform_get_x11_auth(struct X11Display *display, const Config *cfg)\r
24 {\r
25     /* Do nothing, therefore no auth. */\r
26 }\r
27 const int platform_uses_x11_unix_by_default = TRUE;\r
28 \r
29 /* ----------------------------------------------------------------------\r
30  * File access abstraction.\r
31  */\r
32 \r
33 /*\r
34  * Set local current directory. Returns NULL on success, or else an\r
35  * error message which must be freed after printing.\r
36  */\r
37 char *psftp_lcd(char *dir)\r
38 {\r
39     char *ret = NULL;\r
40 \r
41     if (!SetCurrentDirectory(dir)) {\r
42         LPVOID message;\r
43         int i;\r
44         FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |\r
45                       FORMAT_MESSAGE_FROM_SYSTEM |\r
46                       FORMAT_MESSAGE_IGNORE_INSERTS,\r
47                       NULL, GetLastError(),\r
48                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\r
49                       (LPTSTR)&message, 0, NULL);\r
50         i = strcspn((char *)message, "\n");\r
51         ret = dupprintf("%.*s", i, (LPCTSTR)message);\r
52         LocalFree(message);\r
53     }\r
54 \r
55     return ret;\r
56 }\r
57 \r
58 /*\r
59  * Get local current directory. Returns a string which must be\r
60  * freed.\r
61  */\r
62 char *psftp_getcwd(void)\r
63 {\r
64     char *ret = snewn(256, char);\r
65     int len = GetCurrentDirectory(256, ret);\r
66     if (len > 256)\r
67         ret = sresize(ret, len, char);\r
68     GetCurrentDirectory(len, ret);\r
69     return ret;\r
70 }\r
71 \r
72 #define TIME_POSIX_TO_WIN(t, ft) do { \\r
73     ULARGE_INTEGER uli; \\r
74     uli.QuadPart = ((ULONGLONG)(t) + 11644473600ull) * 10000000ull; \\r
75     (ft).dwLowDateTime  = uli.LowPart; \\r
76     (ft).dwHighDateTime = uli.HighPart; \\r
77 } while(0)\r
78 #define TIME_WIN_TO_POSIX(ft, t) do { \\r
79     ULARGE_INTEGER uli; \\r
80     uli.LowPart  = (ft).dwLowDateTime; \\r
81     uli.HighPart = (ft).dwHighDateTime; \\r
82     uli.QuadPart = uli.QuadPart / 10000000ull - 11644473600ull; \\r
83     (t) = (unsigned long) uli.QuadPart; \\r
84 } while(0)\r
85 \r
86 struct RFile {\r
87     HANDLE h;\r
88 };\r
89 \r
90 RFile *open_existing_file(char *name, uint64 *size,\r
91                           unsigned long *mtime, unsigned long *atime)\r
92 {\r
93     HANDLE h;\r
94     RFile *ret;\r
95 \r
96         // FFFTP\r
97         return snew(RFile);\r
98 \r
99     h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL,\r
100                    OPEN_EXISTING, 0, 0);\r
101     if (h == INVALID_HANDLE_VALUE)\r
102         return NULL;\r
103 \r
104     ret = snew(RFile);\r
105     ret->h = h;\r
106 \r
107     if (size)\r
108         size->lo=GetFileSize(h, &(size->hi));\r
109 \r
110     if (mtime || atime) {\r
111         FILETIME actime, wrtime;\r
112         GetFileTime(h, NULL, &actime, &wrtime);\r
113         if (atime)\r
114             TIME_WIN_TO_POSIX(actime, *atime);\r
115         if (mtime)\r
116             TIME_WIN_TO_POSIX(wrtime, *mtime);\r
117     }\r
118 \r
119     return ret;\r
120 }\r
121 \r
122 int read_from_file(RFile *f, void *buffer, int length)\r
123 {\r
124     int ret;\r
125     DWORD read;\r
126         // FFFTP\r
127         return (int)SFTP_ReadThreadDataIO(buffer, length);\r
128     ret = ReadFile(f->h, buffer, length, &read, NULL);\r
129     if (!ret)\r
130         return -1;                     /* error */\r
131     else\r
132         return read;\r
133 }\r
134 \r
135 void close_rfile(RFile *f)\r
136 {\r
137         // FFFTP\r
138 //    CloseHandle(f->h);\r
139     sfree(f);\r
140 }\r
141 \r
142 struct WFile {\r
143     HANDLE h;\r
144 };\r
145 \r
146 WFile *open_new_file(char *name)\r
147 {\r
148     HANDLE h;\r
149     WFile *ret;\r
150 \r
151         // FFFTP\r
152         return snew(WFile);\r
153 \r
154     h = CreateFile(name, GENERIC_WRITE, 0, NULL,\r
155                    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);\r
156     if (h == INVALID_HANDLE_VALUE)\r
157         return NULL;\r
158 \r
159     ret = snew(WFile);\r
160     ret->h = h;\r
161 \r
162     return ret;\r
163 }\r
164 \r
165 WFile *open_existing_wfile(char *name, uint64 *size)\r
166 {\r
167     HANDLE h;\r
168     WFile *ret;\r
169 \r
170         // FFFTP\r
171         return snew(WFile);\r
172 \r
173     h = CreateFile(name, GENERIC_WRITE, FILE_SHARE_READ, NULL,\r
174                    OPEN_EXISTING, 0, 0);\r
175     if (h == INVALID_HANDLE_VALUE)\r
176         return NULL;\r
177 \r
178     ret = snew(WFile);\r
179     ret->h = h;\r
180 \r
181     if (size)\r
182         size->lo=GetFileSize(h, &(size->hi));\r
183 \r
184     return ret;\r
185 }\r
186 \r
187 int write_to_file(WFile *f, void *buffer, int length)\r
188 {\r
189     int ret;\r
190     DWORD written;\r
191         // FFFTP\r
192         return (int)SFTP_WriteThreadDataIO(buffer, length);\r
193     ret = WriteFile(f->h, buffer, length, &written, NULL);\r
194     if (!ret)\r
195         return -1;                     /* error */\r
196     else\r
197         return written;\r
198 }\r
199 \r
200 void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)\r
201 {\r
202     FILETIME actime, wrtime;\r
203         // FFFTP\r
204         return;\r
205     TIME_POSIX_TO_WIN(atime, actime);\r
206     TIME_POSIX_TO_WIN(mtime, wrtime);\r
207     SetFileTime(f->h, NULL, &actime, &wrtime);\r
208 }\r
209 \r
210 void close_wfile(WFile *f)\r
211 {\r
212         // FFFTP\r
213 //    CloseHandle(f->h);\r
214     sfree(f);\r
215 }\r
216 \r
217 /* Seek offset bytes through file, from whence, where whence is\r
218    FROM_START, FROM_CURRENT, or FROM_END */\r
219 int seek_file(WFile *f, uint64 offset, int whence)\r
220 {\r
221     DWORD movemethod;\r
222 \r
223         // FFFTP\r
224         return 0;\r
225 \r
226     switch (whence) {\r
227     case FROM_START:\r
228         movemethod = FILE_BEGIN;\r
229         break;\r
230     case FROM_CURRENT:\r
231         movemethod = FILE_CURRENT;\r
232         break;\r
233     case FROM_END:\r
234         movemethod = FILE_END;\r
235         break;\r
236     default:\r
237         return -1;\r
238     }\r
239 \r
240     SetFilePointer(f->h, offset.lo, &(offset.hi), movemethod);\r
241     \r
242     if (GetLastError() != NO_ERROR)\r
243         return -1;\r
244     else \r
245         return 0;\r
246 }\r
247 \r
248 uint64 get_file_posn(WFile *f)\r
249 {\r
250     uint64 ret;\r
251 \r
252         // FFFTP\r
253         SFTP_GetThreadFilePositon((DWORD*)&ret.lo, (LONG*)&ret.hi);\r
254         return ret;\r
255 \r
256     ret.hi = 0L;\r
257     ret.lo = SetFilePointer(f->h, 0L, &(ret.hi), FILE_CURRENT);\r
258 \r
259     return ret;\r
260 }\r
261 \r
262 int file_type(char *name)\r
263 {\r
264     DWORD attr;\r
265     attr = GetFileAttributes(name);\r
266     /* We know of no `weird' files under Windows. */\r
267     if (attr == (DWORD)-1)\r
268         return FILE_TYPE_NONEXISTENT;\r
269     else if (attr & FILE_ATTRIBUTE_DIRECTORY)\r
270         return FILE_TYPE_DIRECTORY;\r
271     else\r
272         return FILE_TYPE_FILE;\r
273 }\r
274 \r
275 struct DirHandle {\r
276     HANDLE h;\r
277     char *name;\r
278 };\r
279 \r
280 DirHandle *open_directory(char *name)\r
281 {\r
282     HANDLE h;\r
283     WIN32_FIND_DATA fdat;\r
284     char *findfile;\r
285     DirHandle *ret;\r
286 \r
287     /* Enumerate files in dir `foo'. */\r
288     findfile = dupcat(name, "/*", NULL);\r
289     h = FindFirstFile(findfile, &fdat);\r
290     if (h == INVALID_HANDLE_VALUE)\r
291         return NULL;\r
292     sfree(findfile);\r
293 \r
294     ret = snew(DirHandle);\r
295     ret->h = h;\r
296     ret->name = dupstr(fdat.cFileName);\r
297     return ret;\r
298 }\r
299 \r
300 char *read_filename(DirHandle *dir)\r
301 {\r
302     do {\r
303 \r
304         if (!dir->name) {\r
305             WIN32_FIND_DATA fdat;\r
306             int ok = FindNextFile(dir->h, &fdat);\r
307             if (!ok)\r
308                 return NULL;\r
309             else\r
310                 dir->name = dupstr(fdat.cFileName);\r
311         }\r
312 \r
313         assert(dir->name);\r
314         if (dir->name[0] == '.' &&\r
315             (dir->name[1] == '\0' ||\r
316              (dir->name[1] == '.' && dir->name[2] == '\0'))) {\r
317             sfree(dir->name);\r
318             dir->name = NULL;\r
319         }\r
320 \r
321     } while (!dir->name);\r
322 \r
323     if (dir->name) {\r
324         char *ret = dir->name;\r
325         dir->name = NULL;\r
326         return ret;\r
327     } else\r
328         return NULL;\r
329 }\r
330 \r
331 void close_directory(DirHandle *dir)\r
332 {\r
333     FindClose(dir->h);\r
334     if (dir->name)\r
335         sfree(dir->name);\r
336     sfree(dir);\r
337 }\r
338 \r
339 int test_wildcard(char *name, int cmdline)\r
340 {\r
341     HANDLE fh;\r
342     WIN32_FIND_DATA fdat;\r
343 \r
344     /* First see if the exact name exists. */\r
345     if (GetFileAttributes(name) != (DWORD)-1)\r
346         return WCTYPE_FILENAME;\r
347 \r
348     /* Otherwise see if a wildcard match finds anything. */\r
349     fh = FindFirstFile(name, &fdat);\r
350     if (fh == INVALID_HANDLE_VALUE)\r
351         return WCTYPE_NONEXISTENT;\r
352 \r
353     FindClose(fh);\r
354     return WCTYPE_WILDCARD;\r
355 }\r
356 \r
357 struct WildcardMatcher {\r
358     HANDLE h;\r
359     char *name;\r
360     char *srcpath;\r
361 };\r
362 \r
363 /*\r
364  * Return a pointer to the portion of str that comes after the last\r
365  * slash (or backslash or colon, if `local' is TRUE).\r
366  */\r
367 static char *stripslashes(char *str, int local)\r
368 {\r
369     char *p;\r
370 \r
371     if (local) {\r
372         p = strchr(str, ':');\r
373         if (p) str = p+1;\r
374     }\r
375 \r
376     p = strrchr(str, '/');\r
377     if (p) str = p+1;\r
378 \r
379     if (local) {\r
380         p = strrchr(str, '\\');\r
381         if (p) str = p+1;\r
382     }\r
383 \r
384     return str;\r
385 }\r
386 \r
387 WildcardMatcher *begin_wildcard_matching(char *name)\r
388 {\r
389     HANDLE h;\r
390     WIN32_FIND_DATA fdat;\r
391     WildcardMatcher *ret;\r
392     char *last;\r
393 \r
394     h = FindFirstFile(name, &fdat);\r
395     if (h == INVALID_HANDLE_VALUE)\r
396         return NULL;\r
397 \r
398     ret = snew(WildcardMatcher);\r
399     ret->h = h;\r
400     ret->srcpath = dupstr(name);\r
401     last = stripslashes(ret->srcpath, 1);\r
402     *last = '\0';\r
403     if (fdat.cFileName[0] == '.' &&\r
404         (fdat.cFileName[1] == '\0' ||\r
405          (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))\r
406         ret->name = NULL;\r
407     else\r
408         ret->name = dupcat(ret->srcpath, fdat.cFileName, NULL);\r
409 \r
410     return ret;\r
411 }\r
412 \r
413 char *wildcard_get_filename(WildcardMatcher *dir)\r
414 {\r
415     while (!dir->name) {\r
416         WIN32_FIND_DATA fdat;\r
417         int ok = FindNextFile(dir->h, &fdat);\r
418 \r
419         if (!ok)\r
420             return NULL;\r
421 \r
422         if (fdat.cFileName[0] == '.' &&\r
423             (fdat.cFileName[1] == '\0' ||\r
424              (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))\r
425             dir->name = NULL;\r
426         else\r
427             dir->name = dupcat(dir->srcpath, fdat.cFileName, NULL);\r
428     }\r
429 \r
430     if (dir->name) {\r
431         char *ret = dir->name;\r
432         dir->name = NULL;\r
433         return ret;\r
434     } else\r
435         return NULL;\r
436 }\r
437 \r
438 void finish_wildcard_matching(WildcardMatcher *dir)\r
439 {\r
440     FindClose(dir->h);\r
441     if (dir->name)\r
442         sfree(dir->name);\r
443     sfree(dir->srcpath);\r
444     sfree(dir);\r
445 }\r
446 \r
447 int vet_filename(char *name)\r
448 {\r
449     if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':'))\r
450         return FALSE;\r
451 \r
452     if (!name[strspn(name, ".")])      /* entirely composed of dots */\r
453         return FALSE;\r
454 \r
455     return TRUE;\r
456 }\r
457 \r
458 int create_directory(char *name)\r
459 {\r
460     return CreateDirectory(name, NULL) != 0;\r
461 }\r
462 \r
463 char *dir_file_cat(char *dir, char *file)\r
464 {\r
465     return dupcat(dir, "\\", file, NULL);\r
466 }\r
467 \r
468 /* ----------------------------------------------------------------------\r
469  * Platform-specific network handling.\r
470  */\r
471 \r
472 /*\r
473  * Be told what socket we're supposed to be using.\r
474  */\r
475 static SOCKET sftp_ssh_socket = INVALID_SOCKET;\r
476 static HANDLE netevent = INVALID_HANDLE_VALUE;\r
477 char *do_select(SOCKET skt, int startup)\r
478 {\r
479     int events;\r
480     if (startup)\r
481         sftp_ssh_socket = skt;\r
482     else\r
483         sftp_ssh_socket = INVALID_SOCKET;\r
484 \r
485     if (p_WSAEventSelect) {\r
486         if (startup) {\r
487             events = (FD_CONNECT | FD_READ | FD_WRITE |\r
488                       FD_OOB | FD_CLOSE | FD_ACCEPT);\r
489             netevent = CreateEvent(NULL, FALSE, FALSE, NULL);\r
490         } else {\r
491             events = 0;\r
492         }\r
493         if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {\r
494             switch (p_WSAGetLastError()) {\r
495               case WSAENETDOWN:\r
496                 return "Network is down";\r
497               default:\r
498                 return "WSAEventSelect(): unknown error";\r
499             }\r
500         }\r
501     }\r
502     return NULL;\r
503 }\r
504 extern int select_result(WPARAM, LPARAM);\r
505 \r
506 int do_eventsel_loop(HANDLE other_event)\r
507 {\r
508     int n, nhandles, nallhandles, netindex, otherindex;\r
509     long next, ticks;\r
510     HANDLE *handles;\r
511     SOCKET *sklist;\r
512     int skcount;\r
513     long now = GETTICKCOUNT();\r
514 \r
515     if (run_timers(now, &next)) {\r
516         ticks = next - GETTICKCOUNT();\r
517         if (ticks < 0) ticks = 0;  /* just in case */\r
518     } else {\r
519         ticks = INFINITE;\r
520     }\r
521 \r
522     handles = handle_get_events(&nhandles);\r
523     handles = sresize(handles, nhandles+2, HANDLE);\r
524     nallhandles = nhandles;\r
525 \r
526     if (netevent != INVALID_HANDLE_VALUE)\r
527         handles[netindex = nallhandles++] = netevent;\r
528     else\r
529         netindex = -1;\r
530     if (other_event != INVALID_HANDLE_VALUE)\r
531         handles[otherindex = nallhandles++] = other_event;\r
532     else\r
533         otherindex = -1;\r
534 \r
535     n = WaitForMultipleObjects(nallhandles, handles, FALSE, ticks);\r
536 \r
537     if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {\r
538         handle_got_event(handles[n - WAIT_OBJECT_0]);\r
539     } else if (netindex >= 0 && n == WAIT_OBJECT_0 + netindex) {\r
540         WSANETWORKEVENTS things;\r
541         SOCKET socket;\r
542         extern SOCKET first_socket(int *), next_socket(int *);\r
543         extern int select_result(WPARAM, LPARAM);\r
544         int i, socketstate;\r
545 \r
546         /*\r
547          * We must not call select_result() for any socket\r
548          * until we have finished enumerating within the\r
549          * tree. This is because select_result() may close\r
550          * the socket and modify the tree.\r
551          */\r
552         /* Count the active sockets. */\r
553         i = 0;\r
554         for (socket = first_socket(&socketstate);\r
555              socket != INVALID_SOCKET;\r
556              socket = next_socket(&socketstate)) i++;\r
557 \r
558         /* Expand the buffer if necessary. */\r
559         sklist = snewn(i, SOCKET);\r
560 \r
561         /* Retrieve the sockets into sklist. */\r
562         skcount = 0;\r
563         for (socket = first_socket(&socketstate);\r
564              socket != INVALID_SOCKET;\r
565              socket = next_socket(&socketstate)) {\r
566             sklist[skcount++] = socket;\r
567         }\r
568 \r
569         /* Now we're done enumerating; go through the list. */\r
570         for (i = 0; i < skcount; i++) {\r
571             WPARAM wp;\r
572             socket = sklist[i];\r
573             wp = (WPARAM) socket;\r
574             if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {\r
575                 static const struct { int bit, mask; } eventtypes[] = {\r
576                     {FD_CONNECT_BIT, FD_CONNECT},\r
577                     {FD_READ_BIT, FD_READ},\r
578                     {FD_CLOSE_BIT, FD_CLOSE},\r
579                     {FD_OOB_BIT, FD_OOB},\r
580                     {FD_WRITE_BIT, FD_WRITE},\r
581                     {FD_ACCEPT_BIT, FD_ACCEPT},\r
582                 };\r
583                 int e;\r
584 \r
585                 noise_ultralight(socket);\r
586                 noise_ultralight(things.lNetworkEvents);\r
587 \r
588                 for (e = 0; e < lenof(eventtypes); e++)\r
589                     if (things.lNetworkEvents & eventtypes[e].mask) {\r
590                         LPARAM lp;\r
591                         int err = things.iErrorCode[eventtypes[e].bit];\r
592                         lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);\r
593                         select_result(wp, lp);\r
594                     }\r
595             }\r
596         }\r
597 \r
598         sfree(sklist);\r
599     }\r
600 \r
601     sfree(handles);\r
602 \r
603     if (n == WAIT_TIMEOUT) {\r
604         now = next;\r
605     } else {\r
606         now = GETTICKCOUNT();\r
607     }\r
608 \r
609     if (otherindex >= 0 && n == WAIT_OBJECT_0 + otherindex)\r
610         return 1;\r
611 \r
612     return 0;\r
613 }\r
614 \r
615 /*\r
616  * Wait for some network data and process it.\r
617  *\r
618  * We have two variants of this function. One uses select() so that\r
619  * it's compatible with WinSock 1. The other uses WSAEventSelect\r
620  * and MsgWaitForMultipleObjects, so that we can consistently use\r
621  * WSAEventSelect throughout; this enables us to also implement\r
622  * ssh_sftp_get_cmdline() using a parallel mechanism.\r
623  */\r
624 int ssh_sftp_loop_iteration(void)\r
625 {\r
626     if (p_WSAEventSelect == NULL) {\r
627         fd_set readfds;\r
628         int ret;\r
629         long now = GETTICKCOUNT();\r
630 \r
631         if (sftp_ssh_socket == INVALID_SOCKET)\r
632             return -1;                 /* doom */\r
633 \r
634         if (socket_writable(sftp_ssh_socket))\r
635             select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_WRITE);\r
636 \r
637         do {\r
638             long next, ticks;\r
639             struct timeval tv, *ptv;\r
640 \r
641             if (run_timers(now, &next)) {\r
642                 ticks = next - GETTICKCOUNT();\r
643                 if (ticks <= 0)\r
644                     ticks = 1;         /* just in case */\r
645                 tv.tv_sec = ticks / 1000;\r
646                 tv.tv_usec = ticks % 1000 * 1000;\r
647                 ptv = &tv;\r
648             } else {\r
649                 ptv = NULL;\r
650             }\r
651 \r
652             FD_ZERO(&readfds);\r
653             FD_SET(sftp_ssh_socket, &readfds);\r
654             ret = p_select(1, &readfds, NULL, NULL, ptv);\r
655 \r
656             if (ret < 0)\r
657                 return -1;                     /* doom */\r
658             else if (ret == 0)\r
659                 now = next;\r
660             else\r
661                 now = GETTICKCOUNT();\r
662 \r
663         } while (ret == 0);\r
664 \r
665         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);\r
666 \r
667         return 0;\r
668     } else {\r
669         return do_eventsel_loop(INVALID_HANDLE_VALUE);\r
670     }\r
671 }\r
672 \r
673 /*\r
674  * Read a command line from standard input.\r
675  * \r
676  * In the presence of WinSock 2, we can use WSAEventSelect to\r
677  * mediate between the socket and stdin, meaning we can send\r
678  * keepalives and respond to server events even while waiting at\r
679  * the PSFTP command prompt. Without WS2, we fall back to a simple\r
680  * fgets.\r
681  */\r
682 struct command_read_ctx {\r
683     HANDLE event;\r
684     char *line;\r
685 };\r
686 \r
687 static DWORD WINAPI command_read_thread(void *param)\r
688 {\r
689     struct command_read_ctx *ctx = (struct command_read_ctx *) param;\r
690 \r
691     ctx->line = fgetline(stdin);\r
692 \r
693     SetEvent(ctx->event);\r
694 \r
695     return 0;\r
696 }\r
697 \r
698 char *ssh_sftp_get_cmdline(char *prompt, int no_fds_ok)\r
699 {\r
700     int ret;\r
701     struct command_read_ctx actx, *ctx = &actx;\r
702     DWORD threadid;\r
703 \r
704     fputs(prompt, stdout);\r
705     fflush(stdout);\r
706 \r
707         // FFFTP\r
708         return fgetline(stdin);\r
709 \r
710     if ((sftp_ssh_socket == INVALID_SOCKET && no_fds_ok) ||\r
711         p_WSAEventSelect == NULL) {\r
712         return fgetline(stdin);        /* very simple */\r
713     }\r
714 \r
715     /*\r
716      * Create a second thread to read from stdin. Process network\r
717      * and timing events until it terminates.\r
718      */\r
719     ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);\r
720     ctx->line = NULL;\r
721 \r
722     if (!CreateThread(NULL, 0, command_read_thread,\r
723                       ctx, 0, &threadid)) {\r
724         fprintf(stderr, "Unable to create command input thread\n");\r
725         cleanup_exit(1);\r
726     }\r
727 \r
728     do {\r
729         ret = do_eventsel_loop(ctx->event);\r
730 \r
731         /* Error return can only occur if netevent==NULL, and it ain't. */\r
732         assert(ret >= 0);\r
733     } while (ret == 0);\r
734 \r
735     return ctx->line;\r
736 }\r
737 \r
738 /* ----------------------------------------------------------------------\r
739  * Main program. Parse arguments etc.\r
740  */\r
741 int main(int argc, char *argv[])\r
742 {\r
743     int ret;\r
744 \r
745     ret = psftp_main(argc, argv);\r
746 \r
747     return ret;\r
748 }\r