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