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