]> sjero.net Git - wget/blob - src/mswindows.c
[svn] Use WaitForSingleObject to wait for thread termination.
[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 <string.h>
37 #include <assert.h>
38 #include <errno.h>
39 #include <math.h>
40
41 #ifdef HACK_BCC_UTIME_BUG
42 # include <io.h>
43 # include <fcntl.h>
44 # ifdef HAVE_UTIME_H
45 #  include <utime.h>
46 # endif
47 # ifdef HAVE_SYS_UTIME_H
48 #  include <sys/utime.h>
49 # endif
50 #endif
51
52 #include "wget.h"
53 #include "utils.h"
54 #include "url.h"
55
56 #ifndef errno
57 extern int errno;
58 #endif
59
60 #ifndef ES_SYSTEM_REQUIRED
61 #define ES_SYSTEM_REQUIRED  0x00000001
62 #endif
63
64 #ifndef ES_CONTINUOUS
65 #define ES_CONTINUOUS       0x80000000
66 #endif
67
68
69 /* Defined in log.c.  */
70 void log_request_redirect_output PARAMS ((const char *));
71
72 static DWORD set_sleep_mode (DWORD mode);
73
74 static DWORD pwr_mode = 0;
75 static int windows_nt_p;
76
77 #ifndef HAVE_SLEEP
78
79 /* Emulation of Unix sleep.  */
80
81 unsigned int
82 sleep (unsigned seconds)
83 {
84   return SleepEx (1000 * seconds, TRUE) ? 0U : 1000 * seconds;
85 }
86 #endif
87
88 #ifndef HAVE_USLEEP
89 /* Emulation of Unix usleep().  This has a granularity of
90    milliseconds, but that's ok because:
91
92    a) Wget is only using it with milliseconds [not anymore, but b)
93       still applies];
94
95    b) You can't rely on usleep's granularity anyway.  If a caller
96    expects usleep to respect every microsecond, he's in for a
97    surprise.  */
98
99 int
100 usleep (unsigned long usec)
101 {
102   SleepEx (usec / 1000, TRUE);
103   return 0;
104 }
105 #endif  /* HAVE_USLEEP */
106
107 void
108 windows_main_junk (int *argc, char **argv, char **exec_name)
109 {
110   char *p;
111
112   /* Remove .EXE from filename if it has one.  */
113   *exec_name = xstrdup (*exec_name);
114   p = strrchr (*exec_name, '.');
115   if (p)
116     *p = '\0';
117 }
118 \f
119 /* Winsock stuff. */
120
121 static void
122 ws_cleanup (void)
123 {
124   WSACleanup ();
125   if (pwr_mode)
126      set_sleep_mode (pwr_mode);
127   pwr_mode = 0;
128 }
129
130 static void
131 ws_hangup (void)
132 {
133   log_request_redirect_output ("CTRL+Break");
134 }
135
136 void
137 fork_to_background (void)
138 {
139   /* Whether we arrange our own version of opt.lfilename here.  */
140   int changedp = 0;
141
142   if (!opt.lfilename)
143     {
144       opt.lfilename = unique_name (DEFAULT_LOGFILE, 0);
145       changedp = 1;
146     }
147   printf (_("Continuing in background.\n"));
148   if (changedp)
149     printf (_("Output will be written to `%s'.\n"), opt.lfilename);
150
151   ws_hangup ();
152   if (!windows_nt_p)
153     FreeConsole ();
154 }
155
156 static BOOL WINAPI
157 ws_handler (DWORD dwEvent)
158 {
159   switch (dwEvent)
160     {
161 #ifdef CTRLC_BACKGND
162     case CTRL_C_EVENT:
163 #endif
164 #ifdef CTRLBREAK_BACKGND
165     case CTRL_BREAK_EVENT:
166 #endif
167       fork_to_background ();
168       break;
169     case CTRL_SHUTDOWN_EVENT:
170     case CTRL_CLOSE_EVENT:
171     case CTRL_LOGOFF_EVENT:
172     default:
173       ws_cleanup ();
174       return FALSE;
175     }
176   return TRUE;
177 }
178
179 static char *title_buf = NULL;
180 static char *curr_url  = NULL;
181 static int   num_urls  = 0;
182
183 void
184 ws_changetitle (const char *url, int nurl)
185 {
186   if (!nurl)
187     return;
188
189   num_urls = nurl;
190   if (title_buf)
191      xfree(title_buf);
192   if (curr_url)
193      xfree(curr_url);
194   title_buf = (char *)xmalloc (strlen (url) + 20);
195   curr_url = xstrdup(url);
196   sprintf(title_buf, "Wget %s%s", url, nurl == 1 ? "" : " ...");
197   SetConsoleTitle(title_buf);
198 }
199
200 void
201 ws_percenttitle (double percent)
202 {
203   if (num_urls == 1 && title_buf && curr_url && fabs(percent) <= 100.0)
204     {
205       sprintf (title_buf, "Wget [%.0f%%] %s", percent, curr_url);
206       SetConsoleTitle (title_buf);
207     }
208 }
209
210 char *
211 ws_mypath (void)
212 {
213   static char *wspathsave = NULL;
214   char buffer[MAX_PATH];
215   char *ptr;
216
217   if (wspathsave)
218     {
219       return wspathsave;
220     }
221
222   if (GetModuleFileName (NULL, buffer, MAX_PATH) &&
223       (ptr = strrchr (buffer, PATH_SEPARATOR)) != NULL)
224     {
225       *(ptr + 1) = '\0';
226       wspathsave = xstrdup (buffer);
227     }
228   else
229     wspathsave = NULL;
230   return wspathsave;
231 }
232
233 void
234 ws_help (const char *name)
235 {
236   char *mypath = ws_mypath ();
237
238   if (mypath)
239     {
240       struct stat sbuf;
241       char *buf = (char *)alloca (strlen (mypath) + strlen (name) + 4 + 1);
242       sprintf (buf, "%s%s.HLP", mypath, name);
243       if (stat (buf, &sbuf) == 0) 
244         {
245           printf (_("Starting WinHelp %s\n"), buf);
246           WinHelp (NULL, buf, HELP_INDEX, 0);
247         }
248       else
249         {
250           printf ("%s: %s\n", buf, strerror (errno));
251         }
252     }
253 }
254
255 void
256 ws_startup (void)
257 {
258   WORD requested;
259   WSADATA data;
260   int err;
261   OSVERSIONINFO os;
262
263   if (GetVersionEx (&os) == TRUE
264       && os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
265     windows_nt_p = 1;
266
267   requested = MAKEWORD (1, 1);
268   err = WSAStartup (requested, &data);
269
270   if (err != 0)
271     {
272       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
273                exec_name);
274       exit (1);
275     }
276
277   if (data.wVersion < requested)
278     {
279       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
280                exec_name);
281       WSACleanup ();
282       exit (1);
283     }
284   atexit (ws_cleanup);
285   pwr_mode = set_sleep_mode (0);
286   SetConsoleCtrlHandler (ws_handler, TRUE);
287 }
288
289 /* Replacement utime function for buggy Borland C++Builder 5.5 compiler.
290    (The Borland utime function only works on Windows NT.)  */
291
292 #ifdef HACK_BCC_UTIME_BUG
293 int borland_utime(const char *path, const struct utimbuf *times)
294 {
295   int fd;
296   int res;
297   struct ftime ft;
298   struct tm *ptr_tm;
299
300   if ((fd = open (path, O_RDWR)) < 0)
301     return -1;
302
303   ptr_tm = localtime (&times->modtime);
304   ft.ft_tsec = ptr_tm->tm_sec >> 1;
305   ft.ft_min = ptr_tm->tm_min;
306   ft.ft_hour = ptr_tm->tm_hour;
307   ft.ft_day = ptr_tm->tm_mday;
308   ft.ft_month = ptr_tm->tm_mon + 1;
309   ft.ft_year = ptr_tm->tm_year - 80;
310   res = setftime (fd, &ft);
311   close (fd);
312   return res;
313 }
314 #endif
315
316 /*
317  * Prevent Windows entering sleep/hibernation-mode while wget is doing a lengthy transfer.
318  * Windows does by default not consider network activity in console-programs as activity !
319  * Works on Win-98/ME/2K and up.
320  */
321 static
322 DWORD set_sleep_mode (DWORD mode)
323 {
324   HMODULE mod = LoadLibrary ("kernel32.dll");
325   DWORD (*_SetThreadExecutionState) (DWORD) = NULL;
326   DWORD rc = (DWORD)-1;
327
328   if (mod)
329      (void*)_SetThreadExecutionState = GetProcAddress ((HINSTANCE)mod, "SetThreadExecutionState");
330
331   if (_SetThreadExecutionState)
332     {
333       if (mode == 0)  /* first time */
334          mode = (ES_SYSTEM_REQUIRED | ES_CONTINUOUS);
335       rc = (*_SetThreadExecutionState) (mode);
336     }
337   if (mod)
338      FreeLibrary (mod);
339   DEBUGP (("set_sleep_mode(): mode 0x%08lX, rc 0x%08lX\n", mode, rc));
340   return (rc);
341 }
342 \f
343 /* run_with_timeout Windows implementation. */
344
345  /* Stack size 0 uses default thread stack-size (reserve+commit).
346   * Determined by what's in the PE header.
347  */
348 #define THREAD_STACK_SIZE  0
349
350 struct thread_data {
351    void (*fun) (void *);
352    void  *arg;
353    DWORD ws_error; 
354 };
355
356 /* The callback that runs FUN(ARG) in a separate thread.  This
357    function exists for two reasons: a) to not require FUN to be
358    declared WINAPI/__stdcall[1], and b) to retrieve Winsock errors,
359    which are per-thread.  The latter is useful when FUN calls Winsock
360    functions, which is how run_with_timeout is used in Wget.
361
362    [1] MSVC can use __fastcall globally (cl /Gr) and on Watcom this is
363    the default (wcc386 -3r).  */
364
365 static DWORD WINAPI 
366 thread_helper (void *arg)
367 {
368   struct thread_data *td = (struct thread_data *) arg;
369
370   /* Initialize Winsock error to what it was in the parent.  That way
371      the subsequent call to WSAGetLastError will return the same value
372      if td->fun doesn't change Winsock error state.  */
373   WSASetLastError (td->ws_error);
374
375   td->fun (td->arg);
376
377   /* Return Winsock error to the caller, in case FUN ran Winsock
378      code.  */
379   td->ws_error = WSAGetLastError();
380   return 0; 
381 }
382
383 /*
384  * Run FUN with timeout.  This is done by creating a thread for 'fun'
385  * to run in and terminating the thread if it doesn't finish in
386  * SECONDS time.
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   DWORD  thread_id;
395   int    rc = 0;
396
397   DEBUGP (("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
409   thread_arg.fun = fun;
410   thread_arg.arg = arg;
411   thread_arg.ws_error = WSAGetLastError ();
412   thread_hnd = CreateThread (NULL, THREAD_STACK_SIZE, thread_helper,
413                              &thread_arg, 0, &thread_id); 
414   if (!thread_hnd)
415     {
416       DEBUGP (("CreateThread() failed; %s\n", strerror(GetLastError())));
417       goto blocking_fallback;
418     }
419
420   if (WaitForSingleObject(thread_hnd, (DWORD)(1000*seconds)) == WAIT_OBJECT_0)
421     {
422       /* Propagate error state (which is per-thread) to this thread,
423          so the caller can inspect it.  */
424       WSASetLastError (thread_arg.ws_error);
425       DEBUGP (("Winsock error: %d\n", WSAGetLastError()));
426       rc = 0;
427     }
428   else
429     {
430       TerminateThread (thread_hnd, 1);
431       rc = 1;
432     }
433
434   CloseHandle (thread_hnd); /* clear-up after TerminateThread() */
435   thread_hnd = NULL;
436   return rc;
437 }