]> sjero.net Git - wget/blob - src/mswindows.c
[svn] Added docs. 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 #include <config.h>
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <assert.h>
37 #include <errno.h>
38 #include <math.h>
39
40 #ifdef HACK_BCC_UTIME_BUG
41 # include <io.h>
42 # include <fcntl.h>
43 # ifdef HAVE_UTIME_H
44 #  include <utime.h>
45 # endif
46 # ifdef HAVE_SYS_UTIME_H
47 #  include <sys/utime.h>
48 # endif
49 #endif
50
51 #include "wget.h"
52 #include "utils.h"
53 #include "url.h"
54
55 #ifndef errno
56 extern int errno;
57 #endif
58
59 #ifndef ES_SYSTEM_REQUIRED
60 #define ES_SYSTEM_REQUIRED  0x00000001
61 #endif
62
63 #ifndef ES_CONTINUOUS
64 #define ES_CONTINUOUS       0x80000000
65 #endif
66
67
68 /* Defined in log.c.  */
69 void log_request_redirect_output PARAMS ((const char *));
70
71 /* Windows version of xsleep in utils.c.  */
72
73 void
74 xsleep (double seconds)
75 {
76 #ifdef HAVE_USLEEP
77   if (seconds > 1000)
78     {
79       /* Explained in utils.c. */
80       sleep (seconds);
81       seconds -= (long) seconds;
82     }
83   usleep (seconds * 1000000L);
84 #else  /* not HAVE_USLEEP */
85   SleepEx (seconds * 1000, FALSE);
86 #endif /* not HAVE_USLEEP */
87 }
88
89 void
90 windows_main_junk (int *argc, char **argv, char **exec_name)
91 {
92   char *p;
93
94   /* Remove .EXE from filename if it has one.  */
95   *exec_name = xstrdup (*exec_name);
96   p = strrchr (*exec_name, '.');
97   if (p)
98     *p = '\0';
99 }
100 \f
101 static void
102 ws_cleanup (void)
103 {
104   WSACleanup ();
105 }
106
107 static void
108 ws_hangup (const char *reason)
109 {
110   /* Whether we arrange our own version of opt.lfilename here.  */
111   int changedp = 0;
112
113   if (!opt.lfilename)
114     {
115       opt.lfilename = unique_name (DEFAULT_LOGFILE, 0);
116       changedp = 1;
117     }
118   printf (_("Continuing in background.\n"));
119   if (changedp)
120     printf (_("Output will be written to `%s'.\n"), opt.lfilename);
121
122   log_request_redirect_output (reason);
123
124   /* Detach process from the current console.  Under Windows 9x, if we
125      were launched from a 16-bit process (which is usually the case;
126      command.com is 16-bit) the parent process should resume right away.
127      Under NT or if launched from a 32-process under 9x, this is a futile
128      gesture as the parent will wait for us to terminate before resuming.  */
129   FreeConsole ();
130 }
131
132 /* Construct the name for a named section (a.k.a. `file mapping') object.
133    The returned string is dynamically allocated and needs to be xfree()'d.  */
134 static char *
135 make_section_name (DWORD pid)
136 {
137   return aprintf ("gnu_wget_fake_fork_%lu", pid);
138 }
139
140 /* This structure is used to hold all the data that is exchanged between
141    parent and child.  */
142 struct fake_fork_info
143 {
144   HANDLE event;
145   int changedp;
146   char lfilename[MAX_PATH + 1];
147 };
148
149 /* Determines if we are the child and if so performs the child logic.
150    Return values:
151      < 0  error
152        0  parent
153      > 0  child
154 */
155 static int
156 fake_fork_child (void)
157 {
158   HANDLE section, event;
159   struct fake_fork_info *info;
160   char *name;
161   DWORD le;
162
163   name = make_section_name (GetCurrentProcessId ());
164   section = OpenFileMapping (FILE_MAP_WRITE, FALSE, name);
165   le = GetLastError ();
166   xfree (name);
167   if (!section)
168     {
169       if (le == ERROR_FILE_NOT_FOUND)
170         return 0;   /* Section object does not exist; we are the parent.  */
171       else
172         return -1;
173     }
174
175   info = MapViewOfFile (section, FILE_MAP_WRITE, 0, 0, 0);
176   if (!info)
177     {
178       CloseHandle (section);
179       return -1;
180     }
181
182   event = info->event;
183
184   if (!opt.lfilename)
185     {
186       opt.lfilename = unique_name (DEFAULT_LOGFILE, 0);
187       info->changedp = 1;
188       strncpy (info->lfilename, opt.lfilename, sizeof (info->lfilename));
189       info->lfilename[sizeof (info->lfilename) - 1] = '\0';
190     }
191   else
192     info->changedp = 0;
193
194   UnmapViewOfFile (info);
195   CloseHandle (section);
196
197   /* Inform the parent that we've done our part.  */
198   if (!SetEvent (event))
199     return -1;
200
201   CloseHandle (event);
202   return 1;                     /* We are the child.  */
203 }
204
205 /* Windows doesn't support the fork() call; so we fake it by invoking
206    another copy of Wget with the same arguments with which we were
207    invoked.  The child copy of Wget should perform the same initialization
208    sequence as the parent; so we should have two processes that are
209    essentially identical.  We create a specially named section object that
210    allows the child to distinguish itself from the parent and is used to
211    exchange information between the two processes.  We use an event object
212    for synchronization.  */
213 static void
214 fake_fork (void)
215 {
216   char *cmdline, *args;
217   char exe[MAX_PATH + 1];
218   DWORD exe_len, le;
219   SECURITY_ATTRIBUTES sa;
220   HANDLE section, event, h[2];
221   STARTUPINFO si;
222   PROCESS_INFORMATION pi;
223   struct fake_fork_info *info;
224   char *name;
225   BOOL rv;
226
227   event = section = pi.hProcess = pi.hThread = NULL;
228
229   /* Get command line arguments to pass to the child process.
230      We need to skip the name of the command (what amounts to argv[0]).  */
231   cmdline = GetCommandLine ();
232   if (*cmdline == '"')
233     {
234       args = strchr (cmdline + 1, '"');
235       if (args)
236         ++args;
237     }
238   else
239     args = strchr (cmdline, ' ');
240
241   /* It's ok if args is NULL, that would mean there were no arguments
242      after the command name.  As it is now though, we would never get here
243      if that were true.  */
244
245   /* Get the fully qualified name of our executable.  This is more reliable
246      than using argv[0].  */
247   exe_len = GetModuleFileName (GetModuleHandle (NULL), exe, sizeof (exe));
248   if (!exe_len || (exe_len >= sizeof (exe)))
249     return;
250
251   sa.nLength = sizeof (sa);
252   sa.lpSecurityDescriptor = NULL;
253   sa.bInheritHandle = TRUE;
254
255   /* Create an anonymous inheritable event object that starts out
256      non-signaled.  */
257   event = CreateEvent (&sa, FALSE, FALSE, NULL);
258   if (!event)
259     return;
260
261   /* Creat the child process detached form the current console and in a
262      suspended state.  */
263   memset (&si, 0, sizeof (si));
264   si.cb = sizeof (si);
265   rv = CreateProcess (exe, args, NULL, NULL, TRUE, CREATE_SUSPENDED |
266                       DETACHED_PROCESS, NULL, NULL, &si, &pi);
267   if (!rv)
268     goto cleanup;
269
270   /* Create a named section object with a name based on the process id of
271      the child.  */
272   name = make_section_name (pi.dwProcessId);
273   section =
274       CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
275                          sizeof (struct fake_fork_info), name);
276   le = GetLastError();
277   xfree (name);
278   /* Fail if the section object already exists (should not happen).  */
279   if (!section || (le == ERROR_ALREADY_EXISTS))
280     {
281       rv = FALSE;
282       goto cleanup;
283     }
284
285   /* Copy the event handle into the section object.  */
286   info = MapViewOfFile (section, FILE_MAP_WRITE, 0, 0, 0);
287   if (!info)
288     {
289       rv = FALSE;
290       goto cleanup;
291     }
292
293   info->event = event;
294
295   UnmapViewOfFile (info);
296
297   /* Start the child process.  */
298   rv = ResumeThread (pi.hThread);
299   if (!rv)
300     {
301       TerminateProcess (pi.hProcess, (DWORD) -1);
302       goto cleanup;
303     }
304
305   /* Wait for the child to signal to us that it has done its part.  If it
306      terminates before signaling us it's an error.  */
307
308   h[0] = event;
309   h[1] = pi.hProcess;
310   rv = WAIT_OBJECT_0 == WaitForMultipleObjects (2, h, FALSE, 5 * 60 * 1000);
311   if (!rv)
312     goto cleanup;
313
314   info = MapViewOfFile (section, FILE_MAP_READ, 0, 0, 0);
315   if (!info)
316     {
317       rv = FALSE;
318       goto cleanup;
319     }
320
321   /* Ensure string is properly terminated.  */
322   if (info->changedp &&
323       !memchr (info->lfilename, '\0', sizeof (info->lfilename)))
324     {
325       rv = FALSE;
326       goto cleanup;
327     }
328
329   printf (_("Continuing in background, pid %lu.\n"), pi.dwProcessId);
330   if (info->changedp)
331     printf (_("Output will be written to `%s'.\n"), info->lfilename);
332
333   UnmapViewOfFile (info);
334
335 cleanup:
336
337   if (event)
338     CloseHandle (event);
339   if (section)
340     CloseHandle (section);
341   if (pi.hThread)
342     CloseHandle (pi.hThread);
343   if (pi.hProcess)
344     CloseHandle (pi.hProcess);
345
346   /* We're the parent.  If all is well, terminate.  */
347   if (rv)
348     exit (0);
349
350   /* We failed, return.  */
351 }
352
353 /* This is the corresponding Windows implementation of the
354    fork_to_background() function in utils.c.  */
355 void
356 fork_to_background (void)
357 {
358   int rv;
359
360   rv = fake_fork_child ();
361   if (rv < 0)
362     {
363       fprintf (stderr, "fake_fork_child() failed\n");
364       abort ();
365     }
366   else if (rv == 0)
367     {
368       /* We're the parent.  */
369       fake_fork ();
370       /* If fake_fork() returns, it failed.  */
371       fprintf (stderr, "fake_fork() failed\n");
372       abort ();
373     }
374   /* If we get here, we're the child.  */
375 }
376
377 static BOOL WINAPI
378 ws_handler (DWORD dwEvent)
379 {
380   switch (dwEvent)
381     {
382 #ifdef CTRLC_BACKGND
383     case CTRL_C_EVENT:
384       ws_hangup ("CTRL+C");
385       return TRUE;
386 #endif
387 #ifdef CTRLBREAK_BACKGND
388     case CTRL_BREAK_EVENT:
389       ws_hangup ("CTRL+Break");
390       return TRUE;
391 #endif
392     default:
393       return FALSE;
394     }
395 }
396
397 static char *title_buf = NULL;
398 static char *curr_url  = NULL;
399 static int old_percentage = -1;
400
401 /* Updates the console title with the URL of the current file being
402    transferred.  */
403 void
404 ws_changetitle (const char *url)
405 {
406   xfree_null (title_buf);
407   xfree_null (curr_url);
408   title_buf = (char *)xmalloc (strlen (url) + 20);
409   curr_url = xstrdup (url);
410   old_percentage = -1;
411   sprintf (title_buf, "Wget %s", curr_url);
412   SetConsoleTitle (title_buf);
413 }
414
415 /* Updates the console title with the percentage of the current file
416    transferred.  */
417 void
418 ws_percenttitle (double percentage_float)
419 {
420   int percentage;
421
422   if (!title_buf || !curr_url)
423     return;
424
425   percentage = (int) percentage_float;
426
427   /* Clamp percentage value.  */
428   if (percentage < 0)
429     percentage = 0;
430   if (percentage > 100)
431     percentage = 100;
432
433   /* Only update the title when the percentage has changed.  */
434   if (percentage == old_percentage)
435     return;
436
437   old_percentage = percentage;
438
439   sprintf (title_buf, "Wget [%d%%] %s", percentage, curr_url);
440   SetConsoleTitle (title_buf);
441 }
442
443 /* Returns a pointer to the fully qualified name of the directory that
444    contains the Wget binary (wget.exe).  The returned path does not have a
445    trailing path separator.  Returns NULL on failure.  */
446 char *
447 ws_mypath (void)
448 {
449   static char *wspathsave = NULL;
450
451   if (!wspathsave)
452     {
453       char buf[MAX_PATH + 1];
454       char *p;
455       DWORD len;
456
457       len = GetModuleFileName (GetModuleHandle (NULL), buf, sizeof (buf));
458       if (!len || (len >= sizeof (buf)))
459         return NULL;
460
461       p = strrchr (buf, PATH_SEPARATOR);
462       if (!p)
463         return NULL;
464
465       *p = '\0';
466       wspathsave = xstrdup (buf);
467     }
468
469   return wspathsave;
470 }
471
472 /* Prevent Windows entering sleep/hibernation-mode while Wget is doing
473    a lengthy transfer.  Windows does not, by default, consider network
474    activity in console-programs as activity!  Works on Win-98/ME/2K
475    and up.  */
476 static void
477 set_sleep_mode (void)
478 {
479   typedef DWORD (WINAPI *func_t) (DWORD);
480   func_t set_exec_state;
481
482   set_exec_state =
483       (func_t) GetProcAddress (GetModuleHandle ("KERNEL32.DLL"),
484                                "SetThreadExecutionState");
485
486   if (set_exec_state)
487     set_exec_state (ES_SYSTEM_REQUIRED | ES_CONTINUOUS);
488 }
489
490 /* Perform Windows specific initialization.  */
491 void
492 ws_startup (void)
493 {
494   WORD requested;
495   WSADATA data;
496   int err;
497
498   requested = MAKEWORD (1, 1);
499   err = WSAStartup (requested, &data);
500   if (err != 0)
501     {
502       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
503                exec_name);
504       exit (1);
505     }
506
507   if (data.wVersion < requested)
508     {
509       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
510                exec_name);
511       WSACleanup ();
512       exit (1);
513     }
514
515   atexit (ws_cleanup);
516   set_sleep_mode ();
517   SetConsoleCtrlHandler (ws_handler, TRUE);
518 }
519
520 /* Replacement utime function for buggy Borland C++Builder 5.5 compiler.
521    (The Borland utime function only works on Windows NT.)  */
522
523 #ifdef HACK_BCC_UTIME_BUG
524 int
525 borland_utime (const char *path, const struct utimbuf *times)
526 {
527   int fd;
528   int res;
529   struct ftime ft;
530   struct tm *ptr_tm;
531
532   if ((fd = open (path, O_RDWR)) < 0)
533     return -1;
534
535   ptr_tm = localtime (&times->modtime);
536   ft.ft_tsec = ptr_tm->tm_sec >> 1;
537   ft.ft_min = ptr_tm->tm_min;
538   ft.ft_hour = ptr_tm->tm_hour;
539   ft.ft_day = ptr_tm->tm_mday;
540   ft.ft_month = ptr_tm->tm_mon + 1;
541   ft.ft_year = ptr_tm->tm_year - 80;
542   res = setftime (fd, &ft);
543   close (fd);
544   return res;
545 }
546 #endif
547 \f
548 /* run_with_timeout Windows implementation.  */
549
550 /* Stack size 0 uses default thread stack-size (reserve+commit).
551    Determined by what's in the PE header.  */
552 #define THREAD_STACK_SIZE  0
553
554 struct thread_data
555 {
556   void (*fun) (void *);
557   void *arg;
558   DWORD ws_error;
559 };
560
561 /* The callback that runs FUN(ARG) in a separate thread.  This
562    function exists for two reasons: a) to not require FUN to be
563    declared WINAPI/__stdcall[1], and b) to retrieve Winsock errors,
564    which are per-thread.  The latter is useful when FUN calls Winsock
565    functions, which is how run_with_timeout is used in Wget.
566
567    [1] MSVC can use __fastcall globally (cl /Gr) and on Watcom this is
568    the default (wcc386 -3r).  */
569
570 static DWORD WINAPI
571 thread_helper (void *arg)
572 {
573   struct thread_data *td = (struct thread_data *) arg;
574
575   /* Initialize Winsock error to what it was in the parent.  That way
576      the subsequent call to WSAGetLastError will return the same value
577      if td->fun doesn't change Winsock error state.  */
578   WSASetLastError (td->ws_error);
579
580   td->fun (td->arg);
581
582   /* Return Winsock error to the caller, in case FUN ran Winsock
583      code.  */
584   td->ws_error = WSAGetLastError ();
585   return 0;
586 }
587
588 /* Call FUN(ARG), but don't allow it to run for more than TIMEOUT
589    seconds.  Returns non-zero if the function was interrupted with a
590    timeout, zero otherwise.
591
592    This works by running FUN in a separate thread and terminating the
593    thread if it doesn't finish in the specified time.  */
594
595 int
596 run_with_timeout (double seconds, void (*fun) (void *), void *arg)
597 {
598   static HANDLE thread_hnd = NULL;
599   struct thread_data thread_arg;
600   DWORD thread_id;
601   int rc = 0;
602
603   DEBUGP (("seconds %.2f, ", seconds));
604
605   if (seconds == 0)
606     {
607     blocking_fallback:
608       fun (arg);
609       return 0;
610     }
611
612   /* Should never happen, but test for recursivety anyway.  */
613   assert (thread_hnd == NULL);
614
615   thread_arg.fun = fun;
616   thread_arg.arg = arg;
617   thread_arg.ws_error = WSAGetLastError ();
618   thread_hnd = CreateThread (NULL, THREAD_STACK_SIZE, thread_helper,
619                              &thread_arg, 0, &thread_id);
620   if (!thread_hnd)
621     {
622       DEBUGP (("CreateThread() failed; %s\n", strerror (GetLastError ())));
623       goto blocking_fallback;
624     }
625
626   if (WaitForSingleObject (thread_hnd, (DWORD)(1000 * seconds))
627       == WAIT_OBJECT_0)
628     {
629       /* Propagate error state (which is per-thread) to this thread,
630          so the caller can inspect it.  */
631       WSASetLastError (thread_arg.ws_error);
632       DEBUGP (("Winsock error: %d\n", WSAGetLastError ()));
633       rc = 0;
634     }
635   else
636     {
637       TerminateThread (thread_hnd, 1);
638       rc = 1;
639     }
640
641   CloseHandle (thread_hnd);     /* Clear-up after TerminateThread().  */
642   thread_hnd = NULL;
643   return rc;
644 }