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