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