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