]> sjero.net Git - wget/blob - src/mswindows.c
[svn] If strtoll is not present, check for strtoimax and use it where available.
[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   static HANDLE thread_hnd = NULL;
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   /* Should never happen, but test for recursivety anyway.  */
541   assert (thread_hnd == NULL);
542
543   thread_arg.fun = fun;
544   thread_arg.arg = arg;
545   thread_arg.ws_error = WSAGetLastError ();
546   thread_hnd = CreateThread (NULL, THREAD_STACK_SIZE, thread_helper,
547                              &thread_arg, 0, &thread_id);
548   if (!thread_hnd)
549     {
550       DEBUGP (("CreateThread() failed; [0x%x]\n", 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