d6cc41e6 |
1 | /* |
799dfcfa |
2 | * winsftp.c: the Windows-specific parts of PSFTP and PSCP. |
d6cc41e6 |
3 | */ |
4 | |
5 | #include <windows.h> |
799dfcfa |
6 | #ifndef AUTO_WINSOCK |
7 | #ifdef WINSOCK_TWO |
8 | #include <winsock2.h> |
9 | #else |
10 | #include <winsock.h> |
11 | #endif |
12 | #endif |
d6cc41e6 |
13 | |
14 | #include "putty.h" |
15 | #include "psftp.h" |
16 | |
799dfcfa |
17 | /* ---------------------------------------------------------------------- |
18 | * Interface to GUI driver program. |
d6cc41e6 |
19 | */ |
799dfcfa |
20 | |
21 | /* This is just a base value from which the main message numbers are |
22 | * derived. */ |
23 | #define WM_APP_BASE 0x8000 |
24 | |
25 | /* These two pass a single character value in wParam. They represent |
26 | * the visible output from PSCP. */ |
27 | #define WM_STD_OUT_CHAR ( WM_APP_BASE+400 ) |
28 | #define WM_STD_ERR_CHAR ( WM_APP_BASE+401 ) |
29 | |
30 | /* These pass a transfer status update. WM_STATS_CHAR passes a single |
31 | * character in wParam, and is called repeatedly to pass the name of |
32 | * the file, terminated with "\n". WM_STATS_SIZE passes the size of |
33 | * the file being transferred in wParam. WM_STATS_ELAPSED is called |
34 | * to pass the elapsed time (in seconds) in wParam, and |
35 | * WM_STATS_PERCENT passes the percentage of the transfer which is |
36 | * complete, also in wParam. */ |
37 | #define WM_STATS_CHAR ( WM_APP_BASE+402 ) |
38 | #define WM_STATS_SIZE ( WM_APP_BASE+403 ) |
39 | #define WM_STATS_PERCENT ( WM_APP_BASE+404 ) |
40 | #define WM_STATS_ELAPSED ( WM_APP_BASE+405 ) |
41 | |
42 | /* These are used at the end of a run to pass an error code in |
43 | * wParam: zero means success, nonzero means failure. WM_RET_ERR_CNT |
44 | * is used after a copy, and WM_LS_RET_ERR_CNT is used after a file |
45 | * list operation. */ |
46 | #define WM_RET_ERR_CNT ( WM_APP_BASE+406 ) |
47 | #define WM_LS_RET_ERR_CNT ( WM_APP_BASE+407 ) |
48 | |
49 | /* More transfer status update messages. WM_STATS_DONE passes the |
50 | * number of bytes sent so far in wParam. WM_STATS_ETA passes the |
51 | * estimated time to completion (in seconds). WM_STATS_RATEBS passes |
52 | * the average transfer rate (in bytes per second). */ |
53 | #define WM_STATS_DONE ( WM_APP_BASE+408 ) |
54 | #define WM_STATS_ETA ( WM_APP_BASE+409 ) |
55 | #define WM_STATS_RATEBS ( WM_APP_BASE+410 ) |
56 | |
57 | #define NAME_STR_MAX 2048 |
58 | static char statname[NAME_STR_MAX + 1]; |
59 | static unsigned long statsize = 0; |
60 | static unsigned long statdone = 0; |
61 | static unsigned long stateta = 0; |
62 | static unsigned long statratebs = 0; |
63 | static int statperct = 0; |
64 | static unsigned long statelapsed = 0; |
65 | |
66 | static HWND gui_hwnd = NULL; |
67 | |
68 | static void send_msg(HWND h, UINT message, WPARAM wParam) |
d6cc41e6 |
69 | { |
799dfcfa |
70 | while (!PostMessage(h, message, wParam, 0)) |
71 | SleepEx(1000, TRUE); |
d6cc41e6 |
72 | } |
d6cc41e6 |
73 | |
799dfcfa |
74 | void gui_send_char(int is_stderr, int c) |
d6cc41e6 |
75 | { |
799dfcfa |
76 | unsigned int msg_id = WM_STD_OUT_CHAR; |
77 | if (is_stderr) |
78 | msg_id = WM_STD_ERR_CHAR; |
79 | send_msg(gui_hwnd, msg_id, (WPARAM) c); |
80 | } |
d6cc41e6 |
81 | |
799dfcfa |
82 | void gui_send_errcount(int list, int errs) |
83 | { |
84 | unsigned int msg_id = WM_RET_ERR_CNT; |
85 | if (list) |
86 | msg_id = WM_LS_RET_ERR_CNT; |
87 | while (!PostMessage(gui_hwnd, msg_id, (WPARAM) errs, 0)) |
88 | SleepEx(1000, TRUE); |
89 | } |
90 | |
91 | void gui_update_stats(char *name, unsigned long size, |
92 | int percentage, unsigned long elapsed, |
93 | unsigned long done, unsigned long eta, |
94 | unsigned long ratebs) |
95 | { |
96 | unsigned int i; |
97 | |
98 | if (strcmp(name, statname) != 0) { |
99 | for (i = 0; i < strlen(name); ++i) |
100 | send_msg(gui_hwnd, WM_STATS_CHAR, (WPARAM) name[i]); |
101 | send_msg(gui_hwnd, WM_STATS_CHAR, (WPARAM) '\n'); |
102 | strcpy(statname, name); |
d6cc41e6 |
103 | } |
799dfcfa |
104 | if (statsize != size) { |
105 | send_msg(gui_hwnd, WM_STATS_SIZE, (WPARAM) size); |
106 | statsize = size; |
107 | } |
108 | if (statdone != done) { |
109 | send_msg(gui_hwnd, WM_STATS_DONE, (WPARAM) done); |
110 | statdone = done; |
111 | } |
112 | if (stateta != eta) { |
113 | send_msg(gui_hwnd, WM_STATS_ETA, (WPARAM) eta); |
114 | stateta = eta; |
115 | } |
116 | if (statratebs != ratebs) { |
117 | send_msg(gui_hwnd, WM_STATS_RATEBS, (WPARAM) ratebs); |
118 | statratebs = ratebs; |
d6cc41e6 |
119 | } |
799dfcfa |
120 | if (statelapsed != elapsed) { |
121 | send_msg(gui_hwnd, WM_STATS_ELAPSED, (WPARAM) elapsed); |
122 | statelapsed = elapsed; |
123 | } |
124 | if (statperct != percentage) { |
125 | send_msg(gui_hwnd, WM_STATS_PERCENT, (WPARAM) percentage); |
126 | statperct = percentage; |
127 | } |
128 | } |
129 | |
130 | void gui_enable(char *arg) |
131 | { |
132 | gui_hwnd = (HWND) atoi(arg); |
d6cc41e6 |
133 | } |
134 | |
799dfcfa |
135 | /* ---------------------------------------------------------------------- |
136 | * File access abstraction. |
137 | */ |
138 | |
d6cc41e6 |
139 | /* |
140 | * Set local current directory. Returns NULL on success, or else an |
141 | * error message which must be freed after printing. |
142 | */ |
143 | char *psftp_lcd(char *dir) |
144 | { |
145 | char *ret = NULL; |
146 | |
147 | if (!SetCurrentDirectory(dir)) { |
148 | LPVOID message; |
149 | int i; |
150 | FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | |
151 | FORMAT_MESSAGE_FROM_SYSTEM | |
152 | FORMAT_MESSAGE_IGNORE_INSERTS, |
153 | NULL, GetLastError(), |
154 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
155 | (LPTSTR)&message, 0, NULL); |
156 | i = strcspn((char *)message, "\n"); |
157 | ret = dupprintf("%.*s", i, (LPCTSTR)message); |
158 | LocalFree(message); |
159 | } |
160 | |
161 | return ret; |
162 | } |
163 | |
164 | /* |
165 | * Get local current directory. Returns a string which must be |
166 | * freed. |
167 | */ |
168 | char *psftp_getcwd(void) |
169 | { |
170 | char *ret = snewn(256, char); |
171 | int len = GetCurrentDirectory(256, ret); |
172 | if (len > 256) |
173 | ret = sresize(ret, len, char); |
174 | GetCurrentDirectory(len, ret); |
175 | return ret; |
176 | } |
177 | |
799dfcfa |
178 | #define TIME_POSIX_TO_WIN(t, ft) (*(LONGLONG*)&(ft) = \ |
179 | ((LONGLONG) (t) + (LONGLONG) 11644473600) * (LONGLONG) 10000000) |
180 | #define TIME_WIN_TO_POSIX(ft, t) ((t) = (unsigned long) \ |
181 | ((*(LONGLONG*)&(ft)) / (LONGLONG) 10000000 - (LONGLONG) 11644473600)) |
182 | |
183 | struct RFile { |
184 | HANDLE h; |
185 | }; |
186 | |
187 | RFile *open_existing_file(char *name, unsigned long *size, |
188 | unsigned long *mtime, unsigned long *atime) |
189 | { |
190 | HANDLE h; |
191 | RFile *ret; |
192 | |
193 | h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL, |
194 | OPEN_EXISTING, 0, 0); |
195 | if (h == INVALID_HANDLE_VALUE) |
196 | return NULL; |
197 | |
198 | ret = snew(RFile); |
199 | ret->h = h; |
200 | |
201 | if (size) |
202 | *size = GetFileSize(h, NULL); |
203 | |
204 | if (mtime || atime) { |
205 | FILETIME actime, wrtime; |
206 | GetFileTime(h, NULL, &actime, &wrtime); |
207 | if (atime) |
208 | TIME_WIN_TO_POSIX(actime, *atime); |
209 | if (mtime) |
210 | TIME_WIN_TO_POSIX(wrtime, *mtime); |
211 | } |
212 | |
213 | return ret; |
214 | } |
215 | |
216 | int read_from_file(RFile *f, void *buffer, int length) |
217 | { |
218 | int ret, read; |
219 | ret = ReadFile(f->h, buffer, length, &read, NULL); |
220 | if (!ret) |
221 | return -1; /* error */ |
222 | else |
223 | return read; |
224 | } |
225 | |
226 | void close_rfile(RFile *f) |
227 | { |
228 | CloseHandle(f->h); |
229 | sfree(f); |
230 | } |
231 | |
232 | struct WFile { |
233 | HANDLE h; |
234 | }; |
235 | |
236 | WFile *open_new_file(char *name) |
237 | { |
238 | HANDLE h; |
239 | WFile *ret; |
240 | |
241 | h = CreateFile(name, GENERIC_WRITE, 0, NULL, |
242 | CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); |
243 | if (h == INVALID_HANDLE_VALUE) |
244 | return NULL; |
245 | |
246 | ret = snew(WFile); |
247 | ret->h = h; |
248 | |
249 | return ret; |
250 | } |
251 | |
252 | int write_to_file(WFile *f, void *buffer, int length) |
253 | { |
254 | int ret, written; |
255 | ret = WriteFile(f->h, buffer, length, &written, NULL); |
256 | if (!ret) |
257 | return -1; /* error */ |
258 | else |
259 | return written; |
260 | } |
261 | |
262 | void set_file_times(WFile *f, unsigned long mtime, unsigned long atime) |
263 | { |
264 | FILETIME actime, wrtime; |
265 | TIME_POSIX_TO_WIN(atime, actime); |
266 | TIME_POSIX_TO_WIN(mtime, wrtime); |
267 | SetFileTime(f->h, NULL, &actime, &wrtime); |
268 | } |
269 | |
270 | void close_wfile(WFile *f) |
271 | { |
272 | CloseHandle(f->h); |
273 | sfree(f); |
274 | } |
275 | |
276 | int file_type(char *name) |
277 | { |
278 | DWORD attr; |
279 | attr = GetFileAttributes(name); |
280 | /* We know of no `weird' files under Windows. */ |
281 | if (attr == (DWORD)-1) |
282 | return FILE_TYPE_NONEXISTENT; |
283 | else if (attr & FILE_ATTRIBUTE_DIRECTORY) |
284 | return FILE_TYPE_DIRECTORY; |
285 | else |
286 | return FILE_TYPE_FILE; |
287 | } |
288 | |
289 | struct DirHandle { |
290 | HANDLE h; |
291 | char *name; |
292 | }; |
293 | |
294 | DirHandle *open_directory(char *name) |
295 | { |
296 | HANDLE h; |
297 | WIN32_FIND_DATA fdat; |
298 | char *findfile; |
299 | DirHandle *ret; |
300 | |
301 | /* To enumerate files in dir `foo', we search for `foo/*'. */ |
302 | findfile = dupcat(name, "/*", NULL); |
303 | h = FindFirstFile(findfile, &fdat); |
304 | if (h == INVALID_HANDLE_VALUE) |
305 | return NULL; |
306 | |
307 | ret = snew(DirHandle); |
308 | ret->h = h; |
309 | ret->name = dupstr(fdat.cFileName); |
310 | return ret; |
311 | } |
312 | |
313 | char *read_filename(DirHandle *dir) |
314 | { |
315 | if (!dir->name) { |
316 | WIN32_FIND_DATA fdat; |
317 | int ok = FindNextFile(dir->h, &fdat); |
318 | |
319 | if (ok) |
320 | dir->name = dupstr(fdat.cFileName); |
321 | } |
322 | |
323 | if (dir->name) { |
324 | char *ret = dir->name; |
325 | dir->name = NULL; |
326 | return ret; |
327 | } else |
328 | return NULL; |
329 | } |
330 | |
331 | void close_directory(DirHandle *dir) |
332 | { |
333 | FindClose(dir->h); |
334 | if (dir->name) |
335 | sfree(dir->name); |
336 | sfree(dir); |
337 | } |
338 | |
339 | int test_wildcard(char *name, int cmdline) |
340 | { |
341 | HANDLE fh; |
342 | WIN32_FIND_DATA fdat; |
343 | |
344 | /* First see if the exact name exists. */ |
345 | if (GetFileAttributes(name) != (DWORD)-1) |
346 | return WCTYPE_FILENAME; |
347 | |
348 | /* Otherwise see if a wildcard match finds anything. */ |
349 | fh = FindFirstFile(name, &fdat); |
350 | if (fh == INVALID_HANDLE_VALUE) |
351 | return WCTYPE_NONEXISTENT; |
352 | |
353 | FindClose(fh); |
354 | return WCTYPE_WILDCARD; |
355 | } |
356 | |
357 | struct WildcardMatcher { |
358 | HANDLE h; |
359 | char *name; |
360 | char *srcpath; |
361 | }; |
362 | |
363 | /* |
364 | * Return a pointer to the portion of str that comes after the last |
365 | * slash (or backslash or colon, if `local' is TRUE). |
366 | */ |
367 | static char *stripslashes(char *str, int local) |
368 | { |
369 | char *p; |
370 | |
371 | if (local) { |
372 | p = strchr(str, ':'); |
373 | if (p) str = p+1; |
374 | } |
375 | |
376 | p = strrchr(str, '/'); |
377 | if (p) str = p+1; |
378 | |
379 | if (local) { |
380 | p = strrchr(str, '\\'); |
381 | if (p) str = p+1; |
382 | } |
383 | |
384 | return str; |
385 | } |
386 | |
387 | WildcardMatcher *begin_wildcard_matching(char *name) |
388 | { |
389 | HANDLE h; |
390 | WIN32_FIND_DATA fdat; |
391 | WildcardMatcher *ret; |
392 | char *last; |
393 | |
394 | h = FindFirstFile(name, &fdat); |
395 | if (h == INVALID_HANDLE_VALUE) |
396 | return NULL; |
397 | |
398 | ret = snew(WildcardMatcher); |
399 | ret->h = h; |
400 | ret->srcpath = dupstr(name); |
401 | last = stripslashes(ret->srcpath, 1); |
402 | *last = '\0'; |
403 | if (fdat.cFileName[0] == '.' && |
404 | (fdat.cFileName[1] == '\0' || |
405 | (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) |
406 | ret->name = NULL; |
407 | else |
408 | ret->name = dupcat(ret->srcpath, fdat.cFileName, NULL); |
409 | |
410 | return ret; |
411 | } |
412 | |
413 | char *wildcard_get_filename(WildcardMatcher *dir) |
414 | { |
415 | while (!dir->name) { |
416 | WIN32_FIND_DATA fdat; |
417 | int ok = FindNextFile(dir->h, &fdat); |
418 | |
419 | if (!ok) |
420 | return NULL; |
421 | |
422 | if (fdat.cFileName[0] == '.' && |
423 | (fdat.cFileName[1] == '\0' || |
424 | (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) |
425 | dir->name = NULL; |
426 | else |
427 | dir->name = dupcat(dir->srcpath, fdat.cFileName, NULL); |
428 | } |
429 | |
430 | if (dir->name) { |
431 | char *ret = dir->name; |
432 | dir->name = NULL; |
433 | return ret; |
434 | } else |
435 | return NULL; |
436 | } |
437 | |
438 | void finish_wildcard_matching(WildcardMatcher *dir) |
439 | { |
440 | FindClose(dir->h); |
441 | if (dir->name) |
442 | sfree(dir->name); |
443 | sfree(dir->srcpath); |
444 | sfree(dir); |
445 | } |
446 | |
447 | int create_directory(char *name) |
448 | { |
449 | return CreateDirectory(name, NULL) != 0; |
450 | } |
451 | |
452 | /* ---------------------------------------------------------------------- |
453 | * Platform-specific network handling. |
454 | */ |
455 | |
456 | /* |
457 | * Be told what socket we're supposed to be using. |
458 | */ |
459 | static SOCKET sftp_ssh_socket; |
460 | char *do_select(SOCKET skt, int startup) |
461 | { |
462 | if (startup) |
463 | sftp_ssh_socket = skt; |
464 | else |
465 | sftp_ssh_socket = INVALID_SOCKET; |
466 | return NULL; |
467 | } |
468 | extern int select_result(WPARAM, LPARAM); |
469 | |
470 | /* |
471 | * Initialize the WinSock driver. |
472 | */ |
473 | static void init_winsock(void) |
474 | { |
475 | WORD winsock_ver; |
476 | WSADATA wsadata; |
477 | |
478 | winsock_ver = MAKEWORD(1, 1); |
479 | if (WSAStartup(winsock_ver, &wsadata)) { |
480 | fprintf(stderr, "Unable to initialise WinSock"); |
481 | cleanup_exit(1); |
482 | } |
483 | if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) { |
484 | fprintf(stderr, "WinSock version is incompatible with 1.1"); |
485 | cleanup_exit(1); |
486 | } |
487 | } |
488 | |
d6cc41e6 |
489 | /* |
490 | * Wait for some network data and process it. |
491 | */ |
492 | int ssh_sftp_loop_iteration(void) |
493 | { |
494 | fd_set readfds; |
495 | |
496 | if (sftp_ssh_socket == INVALID_SOCKET) |
497 | return -1; /* doom */ |
498 | |
499 | FD_ZERO(&readfds); |
500 | FD_SET(sftp_ssh_socket, &readfds); |
501 | if (select(1, &readfds, NULL, NULL, NULL) < 0) |
502 | return -1; /* doom */ |
503 | |
504 | select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ); |
505 | return 0; |
506 | } |
507 | |
799dfcfa |
508 | /* ---------------------------------------------------------------------- |
d6cc41e6 |
509 | * Main program. Parse arguments etc. |
510 | */ |
511 | int main(int argc, char *argv[]) |
512 | { |
513 | int ret; |
514 | |
515 | init_winsock(); |
516 | ret = psftp_main(argc, argv); |
517 | WSACleanup(); |
518 | |
519 | return ret; |
520 | } |