]> sjero.net Git - wget/blob - src/mswindows.c
[svn] Window-specific implementation of run_with_timeout.
[wget] / src / mswindows.c
1 /* mswindows.c -- Windows-specific support
2    Copyright (C) 1995, 1996, 1997, 1998  Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 In addition, as a special exception, the Free Software Foundation
21 gives permission to link the code of its release of Wget with the
22 OpenSSL project's "OpenSSL" library (or with modified versions of it
23 that use the same license as the "OpenSSL" library), and distribute
24 the linked executables.  You must obey the GNU General Public License
25 in all respects for all of the code used other than "OpenSSL".  If you
26 modify this file, you may extend this exception to your version of the
27 file, but you are not obligated to do so.  If you do not wish to do
28 so, delete this exception statement from your version.  */
29
30 /* #### Someone please document what these functions do!  */
31
32 #include <config.h>
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <winsock.h>
37 #include <string.h>
38 #include <assert.h>
39 #include <errno.h>
40 #include <math.h>
41
42 #ifdef HACK_BCC_UTIME_BUG
43 # include <io.h>
44 # include <fcntl.h>
45 # ifdef HAVE_UTIME_H
46 #  include <utime.h>
47 # endif
48 # ifdef HAVE_SYS_UTIME_H
49 #  include <sys/utime.h>
50 # endif
51 #endif
52
53 #include "wget.h"
54 #include "utils.h"
55 #include "url.h"
56
57 #ifndef errno
58 extern int errno;
59 #endif
60
61 #ifndef ES_SYSTEM_REQUIRED
62 #define ES_SYSTEM_REQUIRED  0x00000001
63 #endif
64
65 #ifndef ES_CONTINUOUS
66 #define ES_CONTINUOUS       0x80000000
67 #endif
68
69
70 /* Defined in log.c.  */
71 void log_request_redirect_output PARAMS ((const char *));
72
73 static DWORD set_sleep_mode (DWORD mode);
74
75 static DWORD pwr_mode = 0;
76 static int windows_nt_p;
77
78 #ifndef HAVE_SLEEP
79
80 /* Emulation of Unix sleep.  */
81
82 unsigned int
83 sleep (unsigned seconds)
84 {
85   return SleepEx (1000 * seconds, TRUE) ? 0U : 1000 * seconds;
86 }
87 #endif
88
89 #ifndef HAVE_USLEEP
90 /* Emulation of Unix usleep().  This has a granularity of
91    milliseconds, but that's ok because:
92
93    a) Wget is only using it with milliseconds [not anymore, but b)
94       still applies];
95
96    b) You can't rely on usleep's granularity anyway.  If a caller
97    expects usleep to respect every microsecond, he's in for a
98    surprise.  */
99
100 int
101 usleep (unsigned long usec)
102 {
103   SleepEx (usec / 1000, TRUE);
104   return 0;
105 }
106 #endif  /* HAVE_USLEEP */
107
108 void
109 windows_main_junk (int *argc, char **argv, char **exec_name)
110 {
111   char *p;
112
113   /* Remove .EXE from filename if it has one.  */
114   *exec_name = xstrdup (*exec_name);
115   p = strrchr (*exec_name, '.');
116   if (p)
117     *p = '\0';
118 }
119 \f
120 /* Winsock stuff. */
121
122 static void
123 ws_cleanup (void)
124 {
125   WSACleanup ();
126   if (pwr_mode)
127      set_sleep_mode (pwr_mode);
128   pwr_mode = 0;
129 }
130
131 static void
132 ws_hangup (void)
133 {
134   log_request_redirect_output ("CTRL+Break");
135 }
136
137 void
138 fork_to_background (void)
139 {
140   /* Whether we arrange our own version of opt.lfilename here.  */
141   int changedp = 0;
142
143   if (!opt.lfilename)
144     {
145       opt.lfilename = unique_name (DEFAULT_LOGFILE, 0);
146       changedp = 1;
147     }
148   printf (_("Continuing in background.\n"));
149   if (changedp)
150     printf (_("Output will be written to `%s'.\n"), opt.lfilename);
151
152   ws_hangup ();
153   if (!windows_nt_p)
154     FreeConsole ();
155 }
156
157 static BOOL WINAPI
158 ws_handler (DWORD dwEvent)
159 {
160   switch (dwEvent)
161     {
162 #ifdef CTRLC_BACKGND
163     case CTRL_C_EVENT:
164 #endif
165 #ifdef CTRLBREAK_BACKGND
166     case CTRL_BREAK_EVENT:
167 #endif
168       fork_to_background ();
169       break;
170     case CTRL_SHUTDOWN_EVENT:
171     case CTRL_CLOSE_EVENT:
172     case CTRL_LOGOFF_EVENT:
173     default:
174       ws_cleanup ();
175       return FALSE;
176     }
177   return TRUE;
178 }
179
180 static char *title_buf = NULL;
181 static char *curr_url  = NULL;
182 static int   num_urls  = 0;
183
184 void
185 ws_changetitle (const char *url, int nurl)
186 {
187   if (!nurl)
188     return;
189
190   num_urls = nurl;
191   if (title_buf)
192      xfree(title_buf);
193   if (curr_url)
194      xfree(curr_url);
195   title_buf = (char *)xmalloc (strlen (url) + 20);
196   curr_url = xstrdup(url);
197   sprintf(title_buf, "Wget %s%s", url, nurl == 1 ? "" : " ...");
198   SetConsoleTitle(title_buf);
199 }
200
201 void
202 ws_percenttitle (double percent)
203 {
204   if (num_urls == 1 && title_buf && curr_url && fabs(percent) <= 100.0)
205     {
206       sprintf (title_buf, "Wget [%.1f%%] %s", percent, curr_url);
207       SetConsoleTitle (title_buf);
208     }
209 }
210
211 char *
212 ws_mypath (void)
213 {
214   static char *wspathsave = NULL;
215   char buffer[MAX_PATH];
216   char *ptr;
217
218   if (wspathsave)
219     {
220       return wspathsave;
221     }
222
223   if (GetModuleFileName (NULL, buffer, MAX_PATH) &&
224       (ptr = strrchr (buffer, PATH_SEPARATOR)) != NULL)
225     {
226       *(ptr + 1) = '\0';
227       wspathsave = xstrdup (buffer);
228     }
229   else
230     wspathsave = NULL;
231   return wspathsave;
232 }
233
234 void
235 ws_help (const char *name)
236 {
237   char *mypath = ws_mypath ();
238
239   if (mypath)
240     {
241       struct stat sbuf;
242       char *buf = (char *)alloca (strlen (mypath) + strlen (name) + 4 + 1);
243       sprintf (buf, "%s%s.HLP", mypath, name);
244       if (stat (buf, &sbuf) == 0) 
245         {
246           printf (_("Starting WinHelp %s\n"), buf);
247           WinHelp (NULL, buf, HELP_INDEX, NULL);
248         }
249       else
250         {
251           printf ("%s: %s\n", buf, strerror (errno));
252         }
253     }
254 }
255
256 void
257 ws_startup (void)
258 {
259   WORD requested;
260   WSADATA data;
261   int err;
262   OSVERSIONINFO os;
263
264   if (GetVersionEx (&os) == TRUE
265       && os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
266     windows_nt_p = 1;
267
268   requested = MAKEWORD (1, 1);
269   err = WSAStartup (requested, &data);
270
271   if (err != 0)
272     {
273       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
274                exec_name);
275       exit (1);
276     }
277
278   if (data.wVersion < requested)
279     {
280       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
281                exec_name);
282       WSACleanup ();
283       exit (1);
284     }
285   atexit (ws_cleanup);
286   pwr_mode = set_sleep_mode (0);
287   SetConsoleCtrlHandler (ws_handler, TRUE);
288 }
289
290 /* Replacement utime function for buggy Borland C++Builder 5.5 compiler.
291    (The Borland utime function only works on Windows NT.)  */
292
293 #ifdef HACK_BCC_UTIME_BUG
294 int borland_utime(const char *path, const struct utimbuf *times)
295 {
296   int fd;
297   int res;
298   struct ftime ft;
299   struct tm *ptr_tm;
300
301   if ((fd = open (path, O_RDWR)) < 0)
302     return -1;
303
304   ptr_tm = localtime (&times->modtime);
305   ft.ft_tsec = ptr_tm->tm_sec >> 1;
306   ft.ft_min = ptr_tm->tm_min;
307   ft.ft_hour = ptr_tm->tm_hour;
308   ft.ft_day = ptr_tm->tm_mday;
309   ft.ft_month = ptr_tm->tm_mon + 1;
310   ft.ft_year = ptr_tm->tm_year - 80;
311   res = setftime (fd, &ft);
312   close (fd);
313   return res;
314 }
315 #endif
316
317 /*
318  * Prevent Windows entering sleep/hibernation-mode while wget is doing a lengthy transfer.
319  * Windows does by default not consider network activity in console-programs as activity !
320  * Works on Win-98/ME/2K and up.
321  */
322 static
323 DWORD set_sleep_mode (DWORD mode)
324 {
325   HMODULE mod = LoadLibrary ("kernel32.dll");
326   DWORD (*_SetThreadExecutionState) (DWORD) = NULL;
327   DWORD rc = (DWORD)-1;
328
329   if (mod)
330      (void*)_SetThreadExecutionState = GetProcAddress ((HINSTANCE)mod, "SetThreadExecutionState");
331
332   if (_SetThreadExecutionState)
333     {
334       if (mode == 0)  /* first time */
335          mode = (ES_SYSTEM_REQUIRED | ES_CONTINUOUS);
336       rc = (*_SetThreadExecutionState) (mode);
337     }
338   if (mod)
339      FreeLibrary (mod);
340   DEBUGP (("set_sleep_mode(): mode 0x%08lX, rc 0x%08lX\n", mode, rc));
341   return (rc);
342 }
343 \f
344 /* run_with_timeout Windows implementation. */
345
346 /* Wait for thread completion in 0.1s intervals (a tradeoff between 
347  * CPU loading and resolution).
348  */
349 #define THREAD_WAIT_INTV   100  
350 #define THREAD_STACK_SIZE  4096 
351
352 struct thread_data {
353    void (*fun) (void *);
354    void  *arg;
355    DWORD ws_error; 
356 };
357
358 static DWORD WINAPI 
359 thread_helper (void *arg)
360 {
361   struct thread_data *td = (struct thread_data *) arg;
362   
363   WSASetLastError (0);
364   td->ws_error = 0;
365   (*td->fun) (td->arg);
366   
367   /* Since run_with_timeout() is only used for Winsock functions and
368    * Winsock errors are per-thread, we must return this to caller.
369    */
370   td->ws_error = WSAGetLastError();
371   return (0); 
372 }
373
374 #ifdef GV_DEBUG  /* I'll remove this eventually */
375 # define DEBUGN(lvl,x)  do { if (opt.verbose >= (lvl)) DEBUGP (x); } while (0)
376 #else
377 # define DEBUGN(lvl,x)  ((void)0)
378 #endif  
379
380 /*
381  * Run FUN with timeout.  This is done by creating a thread for 'fun'
382  * to run in.  Since call-convention of 'fun' is undefined [1], we
383  * must call it via thread_helper() which must be __stdcall/WINAPI.
384  *
385  * [1] MSVC can use __fastcall globally (cl /Gr) and on Watcom this is the
386  *     default (wcc386 -3r). 
387  */
388
389 int
390 run_with_timeout (double seconds, void (*fun) (void *), void *arg)
391 {
392   static HANDLE thread_hnd = NULL;
393   struct thread_data thread_arg;
394   struct wget_timer *timer;
395   DWORD  thread_id, exitCode;
396
397   DEBUGN (2, ("seconds %.2f, ", seconds));
398   
399   if (seconds == 0)
400     {
401     blocking_fallback:
402       fun (arg);
403       return 0;
404     }
405
406   /* Should never happen, but test for recursivety anyway */
407   assert (thread_hnd == NULL);  
408   thread_arg.arg = arg;
409   thread_arg.fun = fun;
410   thread_hnd = CreateThread (NULL, THREAD_STACK_SIZE,
411                              thread_helper, (void*)&thread_arg, 
412                              0, &thread_id); 
413   if (!thread_hnd)
414     {
415       DEBUGP (("CreateThread() failed; %s\n", strerror(GetLastError())));
416       goto blocking_fallback;
417     }
418
419   timer = wtimer_new();
420
421   exitCode = 0;
422
423   /* Keep checking for thread's state until the timeout expires. */
424   while (wtimer_elapsed (timer) < 1000 * seconds)
425     {
426       GetExitCodeThread (thread_hnd, &exitCode);
427       DEBUGN (2, ("thread exit-code %lu\n", exitCode));
428       if (exitCode != STILL_ACTIVE)
429         break;
430       Sleep (THREAD_WAIT_INTV);
431     }
432
433   DEBUGN (2, ("elapsed %.2f, wtimer_elapsed %.2f, ", elapsed,
434               wtimer_elapsed (timer)));
435
436   wtimer_delete (timer);
437
438   /* If we timed out kill the thread. Normal thread exitCode would be 0.
439    */
440   if (exitCode == STILL_ACTIVE)
441     {
442       DEBUGN (2, ("thread timed out\n"));
443       TerminateThread (thread_hnd, 0);
444     }  
445   else
446     {
447       DEBUGN (2, ("thread exit-code %lu, WS error %lu\n", exitCode, thread_arg.ws_error));
448
449       /* Propagate error state (which is per-thread) to this thread,
450          so the caller can inspect it.  */
451       WSASetLastError (thread_arg.ws_error);
452     }
453   thread_hnd = NULL;
454   return exitCode == STILL_ACTIVE;
455 }