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