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