]> sjero.net Git - wget/blob - src/mswindows.c
Don't want to close log if an explicit -o was given. Let reopen to /dev/null handle...
[wget] / src / mswindows.c
1 /* mswindows.c -- Windows-specific support
2    Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
3    2004, 2005, 2006, 2007, 2008 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 3 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, see <http://www.gnu.org/licenses/>.
19
20 Additional permission under GNU GPL version 3 section 7
21
22 If you modify this program, or any covered work, by linking or
23 combining it with the OpenSSL project's OpenSSL library (or a
24 modified version of that library), containing parts covered by the
25 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26 grants you additional permission to convey the resulting work.
27 Corresponding Source for a non-source form of such a combination
28 shall include the source code for the parts of OpenSSL used as well
29 as that of the covered work.  */
30
31 #define INHIBIT_WRAP /* avoid wrapping of socket, bind, ... */
32
33 #include "wget.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
43 #include "utils.h"
44 #include "url.h"
45
46 #ifndef ES_SYSTEM_REQUIRED
47 #define ES_SYSTEM_REQUIRED  0x00000001
48 #endif
49
50 #ifndef ES_CONTINUOUS
51 #define ES_CONTINUOUS       0x80000000
52 #endif
53
54
55 /* Defined in log.c.  */
56 void log_request_redirect_output (const char *);
57
58 /* Windows version of xsleep in utils.c.  */
59
60 void
61 xsleep (double seconds)
62 {
63 #ifdef HAVE_USLEEP
64   if (seconds > 1000)
65     {
66       /* Explained in utils.c. */
67       sleep (seconds);
68       seconds -= (long) seconds;
69     }
70   usleep (seconds * 1000000);
71 #else  /* not HAVE_USLEEP */
72   SleepEx ((DWORD) (seconds * 1000 + .5), FALSE);
73 #endif /* not HAVE_USLEEP */
74 }
75
76 void
77 windows_main (char **exec_name)
78 {
79   char *p;
80
81   /* Remove .EXE from filename if it has one.  */
82   *exec_name = xstrdup (*exec_name);
83   p = strrchr (*exec_name, '.');
84   if (p)
85     *p = '\0';
86 }
87 \f
88 static void
89 ws_cleanup (void)
90 {
91   xfree ((char*)exec_name);
92   WSACleanup ();
93 }
94
95 #if defined(CTRLBREAK_BACKGND) || defined(CTRLC_BACKGND)
96 static void
97 ws_hangup (const char *reason)
98 {
99   fprintf (stderr, _("Continuing in background.\n"));
100   log_request_redirect_output (reason);
101
102   /* Detach process from the current console.  Under Windows 9x, if we
103      were launched from a 16-bit process (which is usually the case;
104      command.com is 16-bit) the parent process should resume right away.
105      Under NT or if launched from a 32-process under 9x, this is a futile
106      gesture as the parent will wait for us to terminate before resuming.  */
107   FreeConsole ();
108 }
109 #endif
110
111 /* Construct the name for a named section (a.k.a. `file mapping') object.
112    The returned string is dynamically allocated and needs to be xfree()'d.  */
113 static char *
114 make_section_name (DWORD pid)
115 {
116   return aprintf ("gnu_wget_fake_fork_%lu", pid);
117 }
118
119 /* This structure is used to hold all the data that is exchanged between
120    parent and child.  */
121 struct fake_fork_info
122 {
123   HANDLE event;
124   bool logfile_changed;
125   char lfilename[MAX_PATH + 1];
126 };
127
128 /* Determines if we are the child and if so performs the child logic.
129    Return values:
130      < 0  error
131        0  parent
132      > 0  child
133 */
134 static int
135 fake_fork_child (void)
136 {
137   HANDLE section, event;
138   struct fake_fork_info *info;
139   char *name;
140
141   name = make_section_name (GetCurrentProcessId ());
142   section = OpenFileMapping (FILE_MAP_WRITE, FALSE, name);
143   xfree (name);
144   /* It seems that Windows 9x and NT set last-error inconsistently when
145      OpenFileMapping() fails; so we assume it failed because the section
146      object does not exist.  */
147   if (!section)
148     return 0;                   /* We are the parent.  */
149
150   info = MapViewOfFile (section, FILE_MAP_WRITE, 0, 0, 0);
151   if (!info)
152     {
153       CloseHandle (section);
154       return -1;
155     }
156
157   event = info->event;
158
159   info->logfile_changed = false;
160   if (!opt.lfilename && (!opt.quiet || opt.server_response))
161     {
162       /* See utils:fork_to_background for explanation. */
163       FILE *new_log_fp = unique_create (DEFAULT_LOGFILE, false, &opt.lfilename);
164       if (new_log_fp)
165         {
166           info->logfile_changed = true;
167           strncpy (info->lfilename, opt.lfilename, sizeof (info->lfilename));
168           info->lfilename[sizeof (info->lfilename) - 1] = '\0';
169           fclose (new_log_fp);
170         }
171     }
172
173   UnmapViewOfFile (info);
174   CloseHandle (section);
175
176   /* Inform the parent that we've done our part.  */
177   if (!SetEvent (event))
178     return -1;
179
180   CloseHandle (event);
181   return 1;                     /* We are the child.  */
182 }
183
184 /* Windows doesn't support the fork() call; so we fake it by invoking
185    another copy of Wget with the same arguments with which we were
186    invoked.  The child copy of Wget should perform the same initialization
187    sequence as the parent; so we should have two processes that are
188    essentially identical.  We create a specially named section object that
189    allows the child to distinguish itself from the parent and is used to
190    exchange information between the two processes.  We use an event object
191    for synchronization.  */
192 static void
193 fake_fork (void)
194 {
195   char exe[MAX_PATH + 1];
196   DWORD exe_len, le;
197   SECURITY_ATTRIBUTES sa;
198   HANDLE section, event, h[2];
199   STARTUPINFO si;
200   PROCESS_INFORMATION pi;
201   struct fake_fork_info *info;
202   char *name;
203   BOOL rv;
204
205   section = pi.hProcess = pi.hThread = NULL;
206
207   /* Get the fully qualified name of our executable.  This is more reliable
208      than using argv[0].  */
209   exe_len = GetModuleFileName (GetModuleHandle (NULL), exe, sizeof (exe));
210   if (!exe_len || (exe_len >= sizeof (exe)))
211     return;
212
213   sa.nLength = sizeof (sa);
214   sa.lpSecurityDescriptor = NULL;
215   sa.bInheritHandle = TRUE;
216
217   /* Create an anonymous inheritable event object that starts out
218      non-signaled.  */
219   event = CreateEvent (&sa, FALSE, FALSE, NULL);
220   if (!event)
221     return;
222
223   /* Create the child process detached form the current console and in a
224      suspended state.  */
225   xzero (si);
226   si.cb = sizeof (si);
227   rv = CreateProcess (exe, GetCommandLine (), NULL, NULL, TRUE,
228                       CREATE_SUSPENDED | DETACHED_PROCESS,
229                       NULL, NULL, &si, &pi);
230   if (!rv)
231     goto cleanup;
232
233   /* Create a named section object with a name based on the process id of
234      the child.  */
235   name = make_section_name (pi.dwProcessId);
236   section =
237       CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
238                          sizeof (struct fake_fork_info), name);
239   le = GetLastError();
240   xfree (name);
241   /* Fail if the section object already exists (should not happen).  */
242   if (!section || (le == ERROR_ALREADY_EXISTS))
243     {
244       rv = FALSE;
245       goto cleanup;
246     }
247
248   /* Copy the event handle into the section object.  */
249   info = MapViewOfFile (section, FILE_MAP_WRITE, 0, 0, 0);
250   if (!info)
251     {
252       rv = FALSE;
253       goto cleanup;
254     }
255
256   info->event = event;
257
258   UnmapViewOfFile (info);
259
260   /* Start the child process.  */
261   rv = ResumeThread (pi.hThread);
262   if (!rv)
263     {
264       TerminateProcess (pi.hProcess, (DWORD) -1);
265       goto cleanup;
266     }
267
268   /* Wait for the child to signal to us that it has done its part.  If it
269      terminates before signaling us it's an error.  */
270
271   h[0] = event;
272   h[1] = pi.hProcess;
273   rv = WAIT_OBJECT_0 == WaitForMultipleObjects (2, h, FALSE, 5 * 60 * 1000);
274   if (!rv)
275     goto cleanup;
276
277   info = MapViewOfFile (section, FILE_MAP_READ, 0, 0, 0);
278   if (!info)
279     {
280       rv = FALSE;
281       goto cleanup;
282     }
283
284   /* Ensure string is properly terminated.  */
285   if (info->logfile_changed &&
286       !memchr (info->lfilename, '\0', sizeof (info->lfilename)))
287     {
288       rv = FALSE;
289       goto cleanup;
290     }
291
292   if (!opt.quiet)
293     printf (_("Continuing in background, pid %lu.\n"), pi.dwProcessId);
294   if (info->logfile_changed)
295     printf (_("Output will be written to `%s'.\n"), info->lfilename);
296
297   UnmapViewOfFile (info);
298
299 cleanup:
300
301   if (event)
302     CloseHandle (event);
303   if (section)
304     CloseHandle (section);
305   if (pi.hThread)
306     CloseHandle (pi.hThread);
307   if (pi.hProcess)
308     CloseHandle (pi.hProcess);
309
310   /* We're the parent.  If all is well, terminate.  */
311   if (rv)
312     exit (0);
313
314   /* We failed, return.  */
315 }
316
317 /* This is the corresponding Windows implementation of the
318    fork_to_background() function in utils.c.  */
319 void
320 fork_to_background (void)
321 {
322   int rv;
323
324   rv = fake_fork_child ();
325   if (rv < 0)
326     {
327       fprintf (stderr, "fake_fork_child() failed\n");
328       abort ();
329     }
330   else if (rv == 0)
331     {
332       /* We're the parent.  */
333       fake_fork ();
334       /* If fake_fork() returns, it failed.  */
335       fprintf (stderr, "fake_fork() failed\n");
336       abort ();
337     }
338   /* If we get here, we're the child.  */
339 }
340
341 static BOOL WINAPI
342 ws_handler (DWORD dwEvent)
343 {
344   switch (dwEvent)
345     {
346 #ifdef CTRLC_BACKGND
347     case CTRL_C_EVENT:
348       ws_hangup ("CTRL+C");
349       return TRUE;
350 #endif
351 #ifdef CTRLBREAK_BACKGND
352     case CTRL_BREAK_EVENT:
353       ws_hangup ("CTRL+Break");
354       return TRUE;
355 #endif
356     default:
357       return FALSE;
358     }
359 }
360
361 static char *title_buf = NULL;
362 static char *curr_url  = NULL;
363 static int old_percentage = -1;
364
365 /* Updates the console title with the URL of the current file being
366    transferred.  */
367 void
368 ws_changetitle (const char *url)
369 {
370   xfree_null (title_buf);
371   xfree_null (curr_url);
372   title_buf = xmalloc (strlen (url) + 20);
373   curr_url = xstrdup (url);
374   old_percentage = -1;
375   sprintf (title_buf, "Wget %s", curr_url);
376   SetConsoleTitle (title_buf);
377 }
378
379 /* Updates the console title with the percentage of the current file
380    transferred.  */
381 void
382 ws_percenttitle (double percentage_float)
383 {
384   int percentage;
385
386   if (!title_buf || !curr_url)
387     return;
388
389   percentage = (int) percentage_float;
390
391   /* Clamp percentage value.  */
392   if (percentage < 0)
393     percentage = 0;
394   if (percentage > 100)
395     percentage = 100;
396
397   /* Only update the title when the percentage has changed.  */
398   if (percentage == old_percentage)
399     return;
400
401   old_percentage = percentage;
402
403   sprintf (title_buf, "Wget [%d%%] %s", percentage, curr_url);
404   SetConsoleTitle (title_buf);
405 }
406
407 /* Returns a pointer to the fully qualified name of the directory that
408    contains the Wget binary (wget.exe).  The returned path does not have a
409    trailing path separator.  Returns NULL on failure.  */
410 char *
411 ws_mypath (void)
412 {
413   static char *wspathsave = NULL;
414
415   if (!wspathsave)
416     {
417       char buf[MAX_PATH + 1];
418       char *p;
419       DWORD len;
420
421       len = GetModuleFileName (GetModuleHandle (NULL), buf, sizeof (buf));
422       if (!len || (len >= sizeof (buf)))
423         return NULL;
424
425       p = strrchr (buf, PATH_SEPARATOR);
426       if (!p)
427         return NULL;
428
429       *p = '\0';
430       wspathsave = xstrdup (buf);
431     }
432
433   return wspathsave;
434 }
435
436 /* Prevent Windows entering sleep/hibernation-mode while Wget is doing
437    a lengthy transfer.  Windows does not, by default, consider network
438    activity in console-programs as activity!  Works on Win-98/ME/2K
439    and up.  */
440 static void
441 set_sleep_mode (void)
442 {
443   typedef DWORD (WINAPI *func_t) (DWORD);
444   func_t set_exec_state;
445
446   set_exec_state =
447       (func_t) GetProcAddress (GetModuleHandle ("KERNEL32.DLL"),
448                                "SetThreadExecutionState");
449
450   if (set_exec_state)
451     set_exec_state (ES_SYSTEM_REQUIRED | ES_CONTINUOUS);
452 }
453
454 /* Perform Windows specific initialization.  */
455 void
456 ws_startup (void)
457 {
458   WSADATA data;
459   WORD requested = MAKEWORD (1, 1);
460   int err = WSAStartup (requested, &data);
461   if (err != 0)
462     {
463       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
464                exec_name);
465       exit (1);
466     }
467
468   if (data.wVersion < requested)
469     {
470       fprintf (stderr, _("%s: Couldn't find usable socket driver.\n"),
471                exec_name);
472       WSACleanup ();
473       exit (1);
474     }
475
476   atexit (ws_cleanup);
477   set_sleep_mode ();
478   SetConsoleCtrlHandler (ws_handler, TRUE);
479 }
480 \f
481 /* run_with_timeout Windows implementation.  */
482
483 /* Stack size 0 uses default thread stack-size (reserve+commit).
484    Determined by what's in the PE header.  */
485 #define THREAD_STACK_SIZE  0
486
487 struct thread_data
488 {
489   void (*fun) (void *);
490   void *arg;
491   DWORD ws_error;
492 };
493
494 /* The callback that runs FUN(ARG) in a separate thread.  This
495    function exists for two reasons: a) to not require FUN to be
496    declared WINAPI/__stdcall[1], and b) to retrieve Winsock errors,
497    which are per-thread.  The latter is useful when FUN calls Winsock
498    functions, which is how run_with_timeout is used in Wget.
499
500    [1] MSVC can use __fastcall globally (cl /Gr) and on Watcom this is
501    the default (wcc386 -3r).  */
502
503 static DWORD WINAPI
504 thread_helper (void *arg)
505 {
506   struct thread_data *td = (struct thread_data *) arg;
507
508   /* Initialize Winsock error to what it was in the parent.  That way
509      the subsequent call to WSAGetLastError will return the same value
510      if td->fun doesn't change Winsock error state.  */
511   WSASetLastError (td->ws_error);
512
513   td->fun (td->arg);
514
515   /* Return Winsock error to the caller, in case FUN ran Winsock
516      code.  */
517   td->ws_error = WSAGetLastError ();
518   return 0;
519 }
520
521 /* Call FUN(ARG), but don't allow it to run for more than TIMEOUT
522    seconds.  Returns true if the function was interrupted with a
523    timeout, false otherwise.
524
525    This works by running FUN in a separate thread and terminating the
526    thread if it doesn't finish in the specified time.  */
527
528 bool
529 run_with_timeout (double seconds, void (*fun) (void *), void *arg)
530 {
531   HANDLE thread_hnd;
532   struct thread_data thread_arg;
533   DWORD thread_id;
534   bool rc;
535
536   DEBUGP (("seconds %.2f, ", seconds));
537
538   if (seconds == 0)
539     {
540     blocking_fallback:
541       fun (arg);
542       return false;
543     }
544
545   thread_arg.fun = fun;
546   thread_arg.arg = arg;
547   thread_arg.ws_error = WSAGetLastError ();
548   thread_hnd = CreateThread (NULL, THREAD_STACK_SIZE, thread_helper,
549                              &thread_arg, 0, &thread_id);
550   if (!thread_hnd)
551     {
552       DEBUGP (("CreateThread() failed; [%#lx]\n",
553                (unsigned long) GetLastError ()));
554       goto blocking_fallback;
555     }
556
557   if (WaitForSingleObject (thread_hnd, (DWORD)(1000 * seconds))
558       == WAIT_OBJECT_0)
559     {
560       /* Propagate error state (which is per-thread) to this thread,
561          so the caller can inspect it.  */
562       WSASetLastError (thread_arg.ws_error);
563       DEBUGP (("Winsock error: %d\n", WSAGetLastError ()));
564       rc = false;
565     }
566   else
567     {
568       TerminateThread (thread_hnd, 1);
569       rc = true;
570     }
571
572   CloseHandle (thread_hnd);     /* Clear-up after TerminateThread().  */
573   thread_hnd = NULL;
574   return rc;
575 }
576 \f
577 /* Wget expects network calls such as connect, recv, send, etc., to set
578    errno on failure.  To achieve that, Winsock calls are wrapped with code
579    that, in case of error, sets errno to the value of WSAGetLastError().
580    In addition, we provide a wrapper around strerror, which recognizes
581    Winsock errors and prints the appropriate error message. */
582
583 /* Define a macro that creates a function definition that wraps FUN into
584    a function that sets errno the way the rest of the code expects. */
585
586 #define WRAP(fun, decl, call) int wrapped_##fun decl {  \
587   int retval = fun call;                                \
588   if (retval < 0)                                       \
589     errno = WSAGetLastError ();                         \
590   return retval;                                        \
591 }
592
593 WRAP (socket, (int domain, int type, int protocol), (domain, type, protocol))
594 WRAP (bind, (int s, struct sockaddr *a, int alen), (s, a, alen))
595 WRAP (connect, (int s, const struct sockaddr *a, int alen), (s, a, alen))
596 WRAP (listen, (int s, int backlog), (s, backlog))
597 WRAP (accept, (int s, struct sockaddr *a, int *alen), (s, a, alen))
598 WRAP (recv, (int s, void *buf, int len, int flags), (s, buf, len, flags))
599 WRAP (send, (int s, const void *buf, int len, int flags), (s, buf, len, flags))
600 WRAP (select, (int n, fd_set *r, fd_set *w, fd_set *e, const struct timeval *tm),
601               (n, r, w, e, tm))
602 WRAP (getsockname, (int s, struct sockaddr *n, int *nlen), (s, n, nlen))
603 WRAP (getpeername, (int s, struct sockaddr *n, int *nlen), (s, n, nlen))
604 WRAP (setsockopt, (int s, int level, int opt, const void *val, int len),
605                   (s, level, opt, val, len))
606 WRAP (closesocket, (int s), (s))
607
608 /* Return the text of the error message for Winsock error WSERR. */
609
610 static const char *
611 get_winsock_error (int wserr)
612 {
613   switch (wserr) {
614   case WSAEINTR:           return "Interrupted system call";
615   case WSAEBADF:           return "Bad file number";
616   case WSAEACCES:          return "Permission denied";
617   case WSAEFAULT:          return "Bad address";
618   case WSAEINVAL:          return "Invalid argument";
619   case WSAEMFILE:          return "Too many open files";
620   case WSAEWOULDBLOCK:     return "Resource temporarily unavailable";
621   case WSAEINPROGRESS:     return "Operation now in progress";
622   case WSAEALREADY:        return "Operation already in progress";
623   case WSAENOTSOCK:        return "Socket operation on nonsocket";
624   case WSAEDESTADDRREQ:    return "Destination address required";
625   case WSAEMSGSIZE:        return "Message too long";
626   case WSAEPROTOTYPE:      return "Protocol wrong type for socket";
627   case WSAENOPROTOOPT:     return "Bad protocol option";
628   case WSAEPROTONOSUPPORT: return "Protocol not supported";
629   case WSAESOCKTNOSUPPORT: return "Socket type not supported";
630   case WSAEOPNOTSUPP:      return "Operation not supported";
631   case WSAEPFNOSUPPORT:    return "Protocol family not supported";
632   case WSAEAFNOSUPPORT:    return "Address family not supported by protocol family";
633   case WSAEADDRINUSE:      return "Address already in use";
634   case WSAEADDRNOTAVAIL:   return "Cannot assign requested address";
635   case WSAENETDOWN:        return "Network is down";
636   case WSAENETUNREACH:     return "Network is unreachable";
637   case WSAENETRESET:       return "Network dropped connection on reset";
638   case WSAECONNABORTED:    return "Software caused connection abort";
639   case WSAECONNRESET:      return "Connection reset by peer";
640   case WSAENOBUFS:         return "No buffer space available";
641   case WSAEISCONN:         return "Socket is already connected";
642   case WSAENOTCONN:        return "Socket is not connected";
643   case WSAESHUTDOWN:       return "Cannot send after socket shutdown";
644   case WSAETOOMANYREFS:    return "Too many references";
645   case WSAETIMEDOUT:       return "Connection timed out";
646   case WSAECONNREFUSED:    return "Connection refused";
647   case WSAELOOP:           return "Too many levels of symbolic links";
648   case WSAENAMETOOLONG:    return "File name too long";
649   case WSAEHOSTDOWN:       return "Host is down";
650   case WSAEHOSTUNREACH:    return "No route to host";
651   case WSAENOTEMPTY:       return "Not empty";
652   case WSAEPROCLIM:        return "Too many processes";
653   case WSAEUSERS:          return "Too many users";
654   case WSAEDQUOT:          return "Bad quota";
655   case WSAESTALE:          return "Something is stale";
656   case WSAEREMOTE:         return "Remote error";
657   case WSAEDISCON:         return "Disconnected";
658
659   /* Extended Winsock errors */
660   case WSASYSNOTREADY:     return "Winsock library is not ready";
661   case WSANOTINITIALISED:  return "Winsock library not initalised";
662   case WSAVERNOTSUPPORTED: return "Winsock version not supported";
663
664   case WSAHOST_NOT_FOUND: return "Host not found";
665   case WSATRY_AGAIN:      return "Host not found, try again";
666   case WSANO_RECOVERY:    return "Unrecoverable error in call to nameserver";
667   case WSANO_DATA:        return "No data record of requested type";
668
669   default:
670     return NULL;
671   }
672 }
673
674 /* Return the error message corresponding to ERR.  This is different
675    from Windows libc strerror() in that it handles Winsock errors
676    correctly.  */
677
678 const char *
679 windows_strerror (int err)
680 {
681   const char *p;
682   if (err >= 0 && err < sys_nerr)
683     return strerror (err);
684   else if ((p = get_winsock_error (err)) != NULL)
685     return p;
686   else
687     {
688       static char buf[32];
689       snprintf (buf, sizeof (buf), "Unknown error %d (%#x)", err, err);
690       return buf;
691     }
692 }
693 \f
694 #ifdef ENABLE_IPV6
695 /* An inet_ntop implementation that uses WSAAddressToString.
696    Prototype complies with POSIX 1003.1-2004.  This is only used under
697    IPv6 because Wget prints IPv4 addresses using inet_ntoa.  */
698
699 const char *
700 inet_ntop (int af, const void *src, char *dst, socklen_t cnt)
701 {
702   /* struct sockaddr can't accomodate struct sockaddr_in6. */
703   union {
704     struct sockaddr_in6 sin6;
705     struct sockaddr_in sin;
706   } sa;
707   DWORD dstlen = cnt;
708   size_t srcsize;
709
710   xzero (sa);
711   switch (af)
712     {
713     case AF_INET:
714       sa.sin.sin_family = AF_INET;
715       sa.sin.sin_addr = *(struct in_addr *) src;
716       srcsize = sizeof (sa.sin);
717       break;
718     case AF_INET6:
719       sa.sin6.sin6_family = AF_INET6;
720       sa.sin6.sin6_addr = *(struct in6_addr *) src;
721       srcsize = sizeof (sa.sin6);
722       break;
723     default:
724       abort ();
725     }
726
727   if (WSAAddressToString ((struct sockaddr *) &sa, srcsize, NULL, dst, &dstlen) != 0)
728     {
729       errno = WSAGetLastError();
730       return NULL;
731     }
732   return (const char *) dst;
733 }
734 #endif