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