]> sjero.net Git - wget/blob - src/mswindows.c
Update copyright lists, conforming to maintainer guidelines
[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 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 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; [%#lx]\n",
550                (unsigned long) GetLastError ()));
551       goto blocking_fallback;
552     }
553
554   if (WaitForSingleObject (thread_hnd, (DWORD)(1000 * seconds))
555       == WAIT_OBJECT_0)
556     {
557       /* Propagate error state (which is per-thread) to this thread,
558          so the caller can inspect it.  */
559       WSASetLastError (thread_arg.ws_error);
560       DEBUGP (("Winsock error: %d\n", WSAGetLastError ()));
561       rc = false;
562     }
563   else
564     {
565       TerminateThread (thread_hnd, 1);
566       rc = true;
567     }
568
569   CloseHandle (thread_hnd);     /* Clear-up after TerminateThread().  */
570   thread_hnd = NULL;
571   return rc;
572 }
573 \f
574 /* Wget expects network calls such as connect, recv, send, etc., to set
575    errno on failure.  To achieve that, Winsock calls are wrapped with code
576    that, in case of error, sets errno to the value of WSAGetLastError().
577    In addition, we provide a wrapper around strerror, which recognizes
578    Winsock errors and prints the appropriate error message. */
579
580 /* Define a macro that creates a function definition that wraps FUN into
581    a function that sets errno the way the rest of the code expects. */
582
583 #define WRAP(fun, decl, call) int wrapped_##fun decl {  \
584   int retval = fun call;                                \
585   if (retval < 0)                                       \
586     errno = WSAGetLastError ();                         \
587   return retval;                                        \
588 }
589
590 WRAP (socket, (int domain, int type, int protocol), (domain, type, protocol))
591 WRAP (bind, (int s, struct sockaddr *a, int alen), (s, a, alen))
592 WRAP (connect, (int s, const struct sockaddr *a, int alen), (s, a, alen))
593 WRAP (listen, (int s, int backlog), (s, backlog))
594 WRAP (accept, (int s, struct sockaddr *a, int *alen), (s, a, alen))
595 WRAP (recv, (int s, void *buf, int len, int flags), (s, buf, len, flags))
596 WRAP (send, (int s, const void *buf, int len, int flags), (s, buf, len, flags))
597 WRAP (select, (int n, fd_set *r, fd_set *w, fd_set *e, const struct timeval *tm),
598               (n, r, w, e, tm))
599 WRAP (getsockname, (int s, struct sockaddr *n, int *nlen), (s, n, nlen))
600 WRAP (getpeername, (int s, struct sockaddr *n, int *nlen), (s, n, nlen))
601 WRAP (setsockopt, (int s, int level, int opt, const void *val, int len),
602                   (s, level, opt, val, len))
603 WRAP (closesocket, (int s), (s))
604
605 /* Return the text of the error message for Winsock error WSERR. */
606
607 static const char *
608 get_winsock_error (int wserr)
609 {
610   switch (wserr) {
611   case WSAEINTR:           return "Interrupted system call";
612   case WSAEBADF:           return "Bad file number";
613   case WSAEACCES:          return "Permission denied";
614   case WSAEFAULT:          return "Bad address";
615   case WSAEINVAL:          return "Invalid argument";
616   case WSAEMFILE:          return "Too many open files";
617   case WSAEWOULDBLOCK:     return "Resource temporarily unavailable";
618   case WSAEINPROGRESS:     return "Operation now in progress";
619   case WSAEALREADY:        return "Operation already in progress";
620   case WSAENOTSOCK:        return "Socket operation on nonsocket";
621   case WSAEDESTADDRREQ:    return "Destination address required";
622   case WSAEMSGSIZE:        return "Message too long";
623   case WSAEPROTOTYPE:      return "Protocol wrong type for socket";
624   case WSAENOPROTOOPT:     return "Bad protocol option";
625   case WSAEPROTONOSUPPORT: return "Protocol not supported";
626   case WSAESOCKTNOSUPPORT: return "Socket type not supported";
627   case WSAEOPNOTSUPP:      return "Operation not supported";
628   case WSAEPFNOSUPPORT:    return "Protocol family not supported";
629   case WSAEAFNOSUPPORT:    return "Address family not supported by protocol family";
630   case WSAEADDRINUSE:      return "Address already in use";
631   case WSAEADDRNOTAVAIL:   return "Cannot assign requested address";
632   case WSAENETDOWN:        return "Network is down";
633   case WSAENETUNREACH:     return "Network is unreachable";
634   case WSAENETRESET:       return "Network dropped connection on reset";
635   case WSAECONNABORTED:    return "Software caused connection abort";
636   case WSAECONNRESET:      return "Connection reset by peer";
637   case WSAENOBUFS:         return "No buffer space available";
638   case WSAEISCONN:         return "Socket is already connected";
639   case WSAENOTCONN:        return "Socket is not connected";
640   case WSAESHUTDOWN:       return "Cannot send after socket shutdown";
641   case WSAETOOMANYREFS:    return "Too many references";
642   case WSAETIMEDOUT:       return "Connection timed out";
643   case WSAECONNREFUSED:    return "Connection refused";
644   case WSAELOOP:           return "Too many levels of symbolic links";
645   case WSAENAMETOOLONG:    return "File name too long";
646   case WSAEHOSTDOWN:       return "Host is down";
647   case WSAEHOSTUNREACH:    return "No route to host";
648   case WSAENOTEMPTY:       return "Not empty";
649   case WSAEPROCLIM:        return "Too many processes";
650   case WSAEUSERS:          return "Too many users";
651   case WSAEDQUOT:          return "Bad quota";
652   case WSAESTALE:          return "Something is stale";
653   case WSAEREMOTE:         return "Remote error";
654   case WSAEDISCON:         return "Disconnected";
655
656   /* Extended Winsock errors */
657   case WSASYSNOTREADY:     return "Winsock library is not ready";
658   case WSANOTINITIALISED:  return "Winsock library not initalised";
659   case WSAVERNOTSUPPORTED: return "Winsock version not supported";
660
661   case WSAHOST_NOT_FOUND: return "Host not found";
662   case WSATRY_AGAIN:      return "Host not found, try again";
663   case WSANO_RECOVERY:    return "Unrecoverable error in call to nameserver";
664   case WSANO_DATA:        return "No data record of requested type";
665
666   default:
667     return NULL;
668   }
669 }
670
671 /* Return the error message corresponding to ERR.  This is different
672    from Windows libc strerror() in that it handles Winsock errors
673    correctly.  */
674
675 const char *
676 windows_strerror (int err)
677 {
678   const char *p;
679   if (err >= 0 && err < sys_nerr)
680     return strerror (err);
681   else if ((p = get_winsock_error (err)) != NULL)
682     return p;
683   else
684     {
685       static char buf[32];
686       snprintf (buf, sizeof (buf), "Unknown error %d (%#x)", err, err);
687       return buf;
688     }
689 }
690 \f
691 #ifdef ENABLE_IPV6
692 /* An inet_ntop implementation that uses WSAAddressToString.
693    Prototype complies with POSIX 1003.1-2004.  This is only used under
694    IPv6 because Wget prints IPv4 addresses using inet_ntoa.  */
695
696 const char *
697 inet_ntop (int af, const void *src, char *dst, socklen_t cnt)
698 {
699   /* struct sockaddr can't accomodate struct sockaddr_in6. */
700   union {
701     struct sockaddr_in6 sin6;
702     struct sockaddr_in sin;
703   } sa;
704   DWORD dstlen = cnt;
705   size_t srcsize;
706
707   xzero (sa);
708   switch (af)
709     {
710     case AF_INET:
711       sa.sin.sin_family = AF_INET;
712       sa.sin.sin_addr = *(struct in_addr *) src;
713       srcsize = sizeof (sa.sin);
714       break;
715     case AF_INET6:
716       sa.sin6.sin6_family = AF_INET6;
717       sa.sin6.sin6_addr = *(struct in6_addr *) src;
718       srcsize = sizeof (sa.sin6);
719       break;
720     default:
721       abort ();
722     }
723
724   if (WSAAddressToString ((struct sockaddr *) &sa, srcsize, NULL, dst, &dstlen) != 0)
725     {
726       errno = WSAGetLastError();
727       return NULL;
728     }
729   return (const char *) dst;
730 }
731 #endif