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