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