]> sjero.net Git - wget/blob - src/mswindows.c
[svn] * mswindows.c (ws_percenttitle): Guard against future changes by
[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 int windows_nt_p;
74
75 /* Windows version of xsleep in utils.c.  */
76
77 void
78 xsleep (double seconds)
79 {
80 #ifdef HAVE_USLEEP
81   if (seconds > 1000)
82     {
83       /* Explained in utils.c. */
84       sleep (seconds);
85       seconds -= (long) seconds;
86     }
87   usleep (seconds * 1000000L);
88 #else  /* not HAVE_USLEEP */
89   SleepEx (seconds * 1000, FALSE);
90 #endif /* not HAVE_USLEEP */
91 }
92
93 void
94 windows_main_junk (int *argc, char **argv, char **exec_name)
95 {
96   char *p;
97
98   /* Remove .EXE from filename if it has one.  */
99   *exec_name = xstrdup (*exec_name);
100   p = strrchr (*exec_name, '.');
101   if (p)
102     *p = '\0';
103 }
104 \f
105 static void
106 ws_cleanup (void)
107 {
108   WSACleanup ();
109 }
110
111 static void
112 ws_hangup (void)
113 {
114   log_request_redirect_output ("CTRL+Break");
115 }
116
117 void
118 fork_to_background (void)
119 {
120   /* Whether we arrange our own version of opt.lfilename here.  */
121   int changedp = 0;
122
123   if (!opt.lfilename)
124     {
125       opt.lfilename = unique_name (DEFAULT_LOGFILE, 0);
126       changedp = 1;
127     }
128   printf (_("Continuing in background.\n"));
129   if (changedp)
130     printf (_("Output will be written to `%s'.\n"), opt.lfilename);
131
132   ws_hangup ();
133   if (!windows_nt_p)
134     FreeConsole ();
135 }
136
137 static BOOL WINAPI
138 ws_handler (DWORD dwEvent)
139 {
140   switch (dwEvent)
141     {
142 #ifdef CTRLC_BACKGND
143     case CTRL_C_EVENT:
144 #endif
145 #ifdef CTRLBREAK_BACKGND
146     case CTRL_BREAK_EVENT:
147 #endif
148       fork_to_background ();
149       break;
150     case CTRL_SHUTDOWN_EVENT:
151     case CTRL_CLOSE_EVENT:
152     case CTRL_LOGOFF_EVENT:
153     default:
154       ws_cleanup ();
155       return FALSE;
156     }
157   return TRUE;
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   OSVERSIONINFO os;
261
262   if (GetVersionEx (&os) == TRUE
263       && os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
264     windows_nt_p = 1;
265
266   requested = MAKEWORD (1, 1);
267   err = WSAStartup (requested, &data);
268   if (err != 0)
269     {
270       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
271                exec_name);
272       exit (1);
273     }
274
275   if (data.wVersion < requested)
276     {
277       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
278                exec_name);
279       WSACleanup ();
280       exit (1);
281     }
282
283   atexit (ws_cleanup);
284   set_sleep_mode ();
285   SetConsoleCtrlHandler (ws_handler, TRUE);
286 }
287
288 /* Replacement utime function for buggy Borland C++Builder 5.5 compiler.
289    (The Borland utime function only works on Windows NT.)  */
290
291 #ifdef HACK_BCC_UTIME_BUG
292 int
293 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 \f
316 /* run_with_timeout Windows implementation.  */
317
318 /* Stack size 0 uses default thread stack-size (reserve+commit).
319    Determined by what's in the PE header.  */
320 #define THREAD_STACK_SIZE  0
321
322 struct thread_data
323 {
324   void (*fun) (void *);
325   void *arg;
326   DWORD ws_error;
327 };
328
329 /* The callback that runs FUN(ARG) in a separate thread.  This
330    function exists for two reasons: a) to not require FUN to be
331    declared WINAPI/__stdcall[1], and b) to retrieve Winsock errors,
332    which are per-thread.  The latter is useful when FUN calls Winsock
333    functions, which is how run_with_timeout is used in Wget.
334
335    [1] MSVC can use __fastcall globally (cl /Gr) and on Watcom this is
336    the default (wcc386 -3r).  */
337
338 static DWORD WINAPI
339 thread_helper (void *arg)
340 {
341   struct thread_data *td = (struct thread_data *) arg;
342
343   /* Initialize Winsock error to what it was in the parent.  That way
344      the subsequent call to WSAGetLastError will return the same value
345      if td->fun doesn't change Winsock error state.  */
346   WSASetLastError (td->ws_error);
347
348   td->fun (td->arg);
349
350   /* Return Winsock error to the caller, in case FUN ran Winsock
351      code.  */
352   td->ws_error = WSAGetLastError ();
353   return 0;
354 }
355
356 /* Call FUN(ARG), but don't allow it to run for more than TIMEOUT
357    seconds.  Returns non-zero if the function was interrupted with a
358    timeout, zero otherwise.
359
360    This works by running FUN in a separate thread and terminating the
361    thread if it doesn't finish in the specified time.  */
362
363 int
364 run_with_timeout (double seconds, void (*fun) (void *), void *arg)
365 {
366   static HANDLE thread_hnd = NULL;
367   struct thread_data thread_arg;
368   DWORD thread_id;
369   int rc = 0;
370
371   DEBUGP (("seconds %.2f, ", seconds));
372
373   if (seconds == 0)
374     {
375     blocking_fallback:
376       fun (arg);
377       return 0;
378     }
379
380   /* Should never happen, but test for recursivety anyway.  */
381   assert (thread_hnd == NULL);
382
383   thread_arg.fun = fun;
384   thread_arg.arg = arg;
385   thread_arg.ws_error = WSAGetLastError ();
386   thread_hnd = CreateThread (NULL, THREAD_STACK_SIZE, thread_helper,
387                              &thread_arg, 0, &thread_id);
388   if (!thread_hnd)
389     {
390       DEBUGP (("CreateThread() failed; %s\n", strerror (GetLastError ())));
391       goto blocking_fallback;
392     }
393
394   if (WaitForSingleObject (thread_hnd, (DWORD)(1000 * seconds))
395       == WAIT_OBJECT_0)
396     {
397       /* Propagate error state (which is per-thread) to this thread,
398          so the caller can inspect it.  */
399       WSASetLastError (thread_arg.ws_error);
400       DEBUGP (("Winsock error: %d\n", WSAGetLastError ()));
401       rc = 0;
402     }
403   else
404     {
405       TerminateThread (thread_hnd, 1);
406       rc = 1;
407     }
408
409   CloseHandle (thread_hnd);     /* Clear-up after TerminateThread().  */
410   thread_hnd = NULL;
411   return rc;
412 }