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