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