]> sjero.net Git - wget/blob - src/mswindows.c
[svn] run_with_timeout docstring synch with utils.c.
[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
294 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
319  * a lengthy transfer.  Windows does by default not consider network
320  * activity in console-programs as activity !  Works on Win-98/ME/2K
321  * and up.
322  */
323 static DWORD
324 set_sleep_mode (DWORD mode)
325 {
326   HMODULE mod = LoadLibrary ("kernel32.dll");
327   DWORD (WINAPI *_SetThreadExecutionState) (DWORD) = NULL;
328   DWORD rc = (DWORD)-1;
329
330   if (mod)
331      (void *)_SetThreadExecutionState
332        = GetProcAddress ((HINSTANCE)mod, "SetThreadExecutionState");
333
334   if (_SetThreadExecutionState)
335     {
336       if (mode == 0)  /* first time */
337         mode = (ES_SYSTEM_REQUIRED | ES_CONTINUOUS);
338       rc = (*_SetThreadExecutionState) (mode);
339     }
340   if (mod)
341     FreeLibrary (mod);
342   DEBUGP (("set_sleep_mode(): mode 0x%08lX, rc 0x%08lX\n", mode, rc));
343   return rc;
344 }
345 \f
346 /* run_with_timeout Windows implementation. */
347
348  /* Stack size 0 uses default thread stack-size (reserve+commit).
349   * Determined by what's in the PE header.
350  */
351 #define THREAD_STACK_SIZE  0
352
353 struct thread_data {
354    void (*fun) (void *);
355    void  *arg;
356    DWORD ws_error; 
357 };
358
359 /* The callback that runs FUN(ARG) in a separate thread.  This
360    function exists for two reasons: a) to not require FUN to be
361    declared WINAPI/__stdcall[1], and b) to retrieve Winsock errors,
362    which are per-thread.  The latter is useful when FUN calls Winsock
363    functions, which is how run_with_timeout is used in Wget.
364
365    [1] MSVC can use __fastcall globally (cl /Gr) and on Watcom this is
366    the default (wcc386 -3r).  */
367
368 static DWORD WINAPI 
369 thread_helper (void *arg)
370 {
371   struct thread_data *td = (struct thread_data *) arg;
372
373   /* Initialize Winsock error to what it was in the parent.  That way
374      the subsequent call to WSAGetLastError will return the same value
375      if td->fun doesn't change Winsock error state.  */
376   WSASetLastError (td->ws_error);
377
378   td->fun (td->arg);
379
380   /* Return Winsock error to the caller, in case FUN ran Winsock
381      code.  */
382   td->ws_error = WSAGetLastError ();
383   return 0; 
384 }
385
386 /* Call FUN(ARG), but don't allow it to run for more than TIMEOUT
387    seconds.  Returns non-zero if the function was interrupted with a
388    timeout, zero otherwise.
389
390    This works by running FUN in a separate thread and terminating the
391    thread if it doesn't finish in the specified time.  */
392
393 int
394 run_with_timeout (double seconds, void (*fun) (void *), void *arg)
395 {
396   static HANDLE thread_hnd = NULL;
397   struct thread_data thread_arg;
398   DWORD  thread_id;
399   int    rc = 0;
400
401   DEBUGP (("seconds %.2f, ", seconds));
402   
403   if (seconds == 0)
404     {
405     blocking_fallback:
406       fun (arg);
407       return 0;
408     }
409
410   /* Should never happen, but test for recursivety anyway */
411   assert (thread_hnd == NULL);  
412
413   thread_arg.fun = fun;
414   thread_arg.arg = arg;
415   thread_arg.ws_error = WSAGetLastError ();
416   thread_hnd = CreateThread (NULL, THREAD_STACK_SIZE, thread_helper,
417                              &thread_arg, 0, &thread_id); 
418   if (!thread_hnd)
419     {
420       DEBUGP (("CreateThread() failed; %s\n", strerror (GetLastError ())));
421       goto blocking_fallback;
422     }
423
424   if (WaitForSingleObject (thread_hnd, (DWORD)(1000 * seconds))
425       == WAIT_OBJECT_0)
426     {
427       /* Propagate error state (which is per-thread) to this thread,
428          so the caller can inspect it.  */
429       WSASetLastError (thread_arg.ws_error);
430       DEBUGP (("Winsock error: %d\n", WSAGetLastError ()));
431       rc = 0;
432     }
433   else
434     {
435       TerminateThread (thread_hnd, 1);
436       rc = 1;
437     }
438
439   CloseHandle (thread_hnd); /* clear-up after TerminateThread() */
440   thread_hnd = NULL;
441   return rc;
442 }