]> sjero.net Git - wget/blob - src/mswindows.c
[svn] Fixed bugs in Windows console event handling.
[wget] / src / mswindows.c
1 /* mswindows.c -- Windows-specific support
2    Copyright (C) 1995, 1996, 1997, 1998, 2004
3    Free Software Foundation, Inc.
4
5 This file is part of GNU Wget.
6
7 GNU Wget is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GNU Wget is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Wget; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21 In addition, as a special exception, the Free Software Foundation
22 gives permission to link the code of its release of Wget with the
23 OpenSSL project's "OpenSSL" library (or with modified versions of it
24 that use the same license as the "OpenSSL" library), and distribute
25 the linked executables.  You must obey the GNU General Public License
26 in all respects for all of the code used other than "OpenSSL".  If you
27 modify this file, you may extend this exception to your version of the
28 file, but you are not obligated to do so.  If you do not wish to do
29 so, delete this exception statement from your version.  */
30
31 /* #### Someone please document what these functions do!  */
32
33 #include <config.h>
34
35 #include <stdio.h>
36 #include <stdlib.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 /* Windows version of xsleep in utils.c.  */
74
75 void
76 xsleep (double seconds)
77 {
78 #ifdef HAVE_USLEEP
79   if (seconds > 1000)
80     {
81       /* Explained in utils.c. */
82       sleep (seconds);
83       seconds -= (long) seconds;
84     }
85   usleep (seconds * 1000000L);
86 #else  /* not HAVE_USLEEP */
87   SleepEx (seconds * 1000, FALSE);
88 #endif /* not HAVE_USLEEP */
89 }
90
91 void
92 windows_main_junk (int *argc, char **argv, char **exec_name)
93 {
94   char *p;
95
96   /* Remove .EXE from filename if it has one.  */
97   *exec_name = xstrdup (*exec_name);
98   p = strrchr (*exec_name, '.');
99   if (p)
100     *p = '\0';
101 }
102 \f
103 static void
104 ws_cleanup (void)
105 {
106   WSACleanup ();
107 }
108
109 static void
110 ws_hangup (const char *reason)
111 {
112   /* Whether we arrange our own version of opt.lfilename here.  */
113   int changedp = 0;
114
115   if (!opt.lfilename)
116     {
117       opt.lfilename = unique_name (DEFAULT_LOGFILE, 0);
118       changedp = 1;
119     }
120   printf (_("Continuing in background.\n"));
121   if (changedp)
122     printf (_("Output will be written to `%s'.\n"), opt.lfilename);
123
124   log_request_redirect_output (reason);
125
126   /* Detach process from the current console.  Under Windows 9x, if we
127      were launched from a 16-bit process (which is usually the case;
128      command.com is 16-bit) the parent process should resume right away.
129      Under NT or if launched from a 32-process under 9x, this is a futile
130      gesture as the parent will wait for us to terminate before resuming.  */
131   FreeConsole ();
132 }
133
134 void
135 fork_to_background (void)
136 {
137   ws_hangup ("fork");
138 }
139
140 static BOOL WINAPI
141 ws_handler (DWORD dwEvent)
142 {
143   switch (dwEvent)
144     {
145 #ifdef CTRLC_BACKGND
146     case CTRL_C_EVENT:
147       ws_hangup ("CTRL+C");
148       return TRUE;
149 #endif
150 #ifdef CTRLBREAK_BACKGND
151     case CTRL_BREAK_EVENT:
152       ws_hangup ("CTRL+Break");
153       return TRUE;
154 #endif
155     default:
156       return FALSE;
157     }
158 }
159
160 static char *title_buf = NULL;
161 static char *curr_url  = NULL;
162 static int old_percentage = -1;
163
164 /* Updates the console title with the URL of the current file being
165    transferred.  */
166 void
167 ws_changetitle (const char *url)
168 {
169   xfree_null (title_buf);
170   xfree_null (curr_url);
171   title_buf = (char *)xmalloc (strlen (url) + 20);
172   curr_url = xstrdup (url);
173   old_percentage = -1;
174   sprintf (title_buf, "Wget %s", curr_url);
175   SetConsoleTitle (title_buf);
176 }
177
178 /* Updates the console title with the percentage of the current file
179    transferred.  */
180 void
181 ws_percenttitle (double percentage_float)
182 {
183   int percentage;
184
185   if (!title_buf || !curr_url)
186     return;
187
188   percentage = (int) percentage_float;
189
190   /* Clamp percentage value.  */
191   if (percentage < 0)
192     percentage = 0;
193   if (percentage > 100)
194     percentage = 100;
195
196   /* Only update the title when the percentage has changed.  */
197   if (percentage == old_percentage)
198     return;
199
200   old_percentage = percentage;
201
202   sprintf (title_buf, "Wget [%d%%] %s", percentage, curr_url);
203   SetConsoleTitle (title_buf);
204 }
205
206 /* Returns a pointer to the fully qualified name of the directory that
207    contains the Wget binary (wget.exe).  The returned path does not have a
208    trailing path separator.  Returns NULL on failure.  */
209 char *
210 ws_mypath (void)
211 {
212   static char *wspathsave = NULL;
213
214   if (!wspathsave)
215     {
216       char buf[MAX_PATH + 1];
217       char *p;
218       DWORD len;
219
220       len = GetModuleFileName (GetModuleHandle (NULL), buf, sizeof (buf));
221       if (!len || (len >= sizeof (buf)))
222         return NULL;
223
224       p = strrchr (buf, PATH_SEPARATOR);
225       if (!p)
226         return NULL;
227
228       *p = '\0';
229       wspathsave = xstrdup (buf);
230     }
231
232   return wspathsave;
233 }
234
235 /* Prevent Windows entering sleep/hibernation-mode while Wget is doing
236    a lengthy transfer.  Windows does not, by default, consider network
237    activity in console-programs as activity!  Works on Win-98/ME/2K
238    and up.  */
239 static void
240 set_sleep_mode (void)
241 {
242   typedef DWORD (WINAPI *func_t) (DWORD);
243   func_t set_exec_state;
244
245   set_exec_state =
246       (func_t) GetProcAddress (GetModuleHandle ("KERNEL32.DLL"),
247                                "SetThreadExecutionState");
248
249   if (set_exec_state)
250     set_exec_state (ES_SYSTEM_REQUIRED | ES_CONTINUOUS);
251 }
252
253 /* Perform Windows specific initialization.  */
254 void
255 ws_startup (void)
256 {
257   WORD requested;
258   WSADATA data;
259   int err;
260
261   requested = MAKEWORD (1, 1);
262   err = WSAStartup (requested, &data);
263   if (err != 0)
264     {
265       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
266                exec_name);
267       exit (1);
268     }
269
270   if (data.wVersion < requested)
271     {
272       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
273                exec_name);
274       WSACleanup ();
275       exit (1);
276     }
277
278   atexit (ws_cleanup);
279   set_sleep_mode ();
280   SetConsoleCtrlHandler (ws_handler, TRUE);
281 }
282
283 /* Replacement utime function for buggy Borland C++Builder 5.5 compiler.
284    (The Borland utime function only works on Windows NT.)  */
285
286 #ifdef HACK_BCC_UTIME_BUG
287 int
288 borland_utime (const char *path, const struct utimbuf *times)
289 {
290   int fd;
291   int res;
292   struct ftime ft;
293   struct tm *ptr_tm;
294
295   if ((fd = open (path, O_RDWR)) < 0)
296     return -1;
297
298   ptr_tm = localtime (&times->modtime);
299   ft.ft_tsec = ptr_tm->tm_sec >> 1;
300   ft.ft_min = ptr_tm->tm_min;
301   ft.ft_hour = ptr_tm->tm_hour;
302   ft.ft_day = ptr_tm->tm_mday;
303   ft.ft_month = ptr_tm->tm_mon + 1;
304   ft.ft_year = ptr_tm->tm_year - 80;
305   res = setftime (fd, &ft);
306   close (fd);
307   return res;
308 }
309 #endif
310 \f
311 /* run_with_timeout Windows implementation.  */
312
313 /* Stack size 0 uses default thread stack-size (reserve+commit).
314    Determined by what's in the PE header.  */
315 #define THREAD_STACK_SIZE  0
316
317 struct thread_data
318 {
319   void (*fun) (void *);
320   void *arg;
321   DWORD ws_error;
322 };
323
324 /* The callback that runs FUN(ARG) in a separate thread.  This
325    function exists for two reasons: a) to not require FUN to be
326    declared WINAPI/__stdcall[1], and b) to retrieve Winsock errors,
327    which are per-thread.  The latter is useful when FUN calls Winsock
328    functions, which is how run_with_timeout is used in Wget.
329
330    [1] MSVC can use __fastcall globally (cl /Gr) and on Watcom this is
331    the default (wcc386 -3r).  */
332
333 static DWORD WINAPI
334 thread_helper (void *arg)
335 {
336   struct thread_data *td = (struct thread_data *) arg;
337
338   /* Initialize Winsock error to what it was in the parent.  That way
339      the subsequent call to WSAGetLastError will return the same value
340      if td->fun doesn't change Winsock error state.  */
341   WSASetLastError (td->ws_error);
342
343   td->fun (td->arg);
344
345   /* Return Winsock error to the caller, in case FUN ran Winsock
346      code.  */
347   td->ws_error = WSAGetLastError ();
348   return 0;
349 }
350
351 /* Call FUN(ARG), but don't allow it to run for more than TIMEOUT
352    seconds.  Returns non-zero if the function was interrupted with a
353    timeout, zero otherwise.
354
355    This works by running FUN in a separate thread and terminating the
356    thread if it doesn't finish in the specified time.  */
357
358 int
359 run_with_timeout (double seconds, void (*fun) (void *), void *arg)
360 {
361   static HANDLE thread_hnd = NULL;
362   struct thread_data thread_arg;
363   DWORD thread_id;
364   int rc = 0;
365
366   DEBUGP (("seconds %.2f, ", seconds));
367
368   if (seconds == 0)
369     {
370     blocking_fallback:
371       fun (arg);
372       return 0;
373     }
374
375   /* Should never happen, but test for recursivety anyway.  */
376   assert (thread_hnd == NULL);
377
378   thread_arg.fun = fun;
379   thread_arg.arg = arg;
380   thread_arg.ws_error = WSAGetLastError ();
381   thread_hnd = CreateThread (NULL, THREAD_STACK_SIZE, thread_helper,
382                              &thread_arg, 0, &thread_id);
383   if (!thread_hnd)
384     {
385       DEBUGP (("CreateThread() failed; %s\n", strerror (GetLastError ())));
386       goto blocking_fallback;
387     }
388
389   if (WaitForSingleObject (thread_hnd, (DWORD)(1000 * seconds))
390       == WAIT_OBJECT_0)
391     {
392       /* Propagate error state (which is per-thread) to this thread,
393          so the caller can inspect it.  */
394       WSASetLastError (thread_arg.ws_error);
395       DEBUGP (("Winsock error: %d\n", WSAGetLastError ()));
396       rc = 0;
397     }
398   else
399     {
400       TerminateThread (thread_hnd, 1);
401       rc = 1;
402     }
403
404   CloseHandle (thread_hnd);     /* Clear-up after TerminateThread().  */
405   thread_hnd = NULL;
406   return rc;
407 }