]> sjero.net Git - wget/blob - src/connect.c
[svn] Explicitly check that fd>=0 in register_extended.
[wget] / src / connect.c
1 /* Establishing and handling network connections.
2    Copyright (C) 1995, 1996, 1997, 2001, 2002 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
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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 <sys/types.h>
35 #ifdef HAVE_UNISTD_H
36 # include <unistd.h>
37 #endif
38 #include <assert.h>
39
40 #ifndef WINDOWS
41 # include <sys/socket.h>
42 # include <netdb.h>
43 # include <netinet/in.h>
44 # ifndef __BEOS__
45 #  include <arpa/inet.h>
46 # endif
47 #endif /* not WINDOWS */
48
49 #include <errno.h>
50 #ifdef HAVE_STRING_H
51 # include <string.h>
52 #else
53 # include <strings.h>
54 #endif /* HAVE_STRING_H */
55 #ifdef HAVE_SYS_SELECT_H
56 # include <sys/select.h>
57 #endif /* HAVE_SYS_SELECT_H */
58
59 #include "wget.h"
60 #include "utils.h"
61 #include "host.h"
62 #include "connect.h"
63 #include "hash.h"
64
65 #ifndef errno
66 extern int errno;
67 #endif
68
69 \f
70 /* Fill SA as per the data in IP and PORT.  SA shoult point to struct
71    sockaddr_storage if ENABLE_IPV6 is defined, to struct sockaddr_in
72    otherwise.  */
73
74 static void
75 sockaddr_set_data (struct sockaddr *sa, const ip_address *ip, int port)
76 {
77   switch (ip->type)
78     {
79     case IPV4_ADDRESS:
80       {
81         struct sockaddr_in *sin = (struct sockaddr_in *)sa;
82         sin->sin_family = AF_INET;
83         sin->sin_port = htons (port);
84         sin->sin_addr = ADDRESS_IPV4_IN_ADDR (ip);
85         break;
86       }
87 #ifdef ENABLE_IPV6
88     case IPV6_ADDRESS:
89       {
90         struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
91         sin6->sin6_family = AF_INET6;
92         sin6->sin6_port = htons (port);
93         sin6->sin6_addr = ADDRESS_IPV6_IN6_ADDR (ip);
94 #ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
95         sin6->sin6_scope_id = ADDRESS_IPV6_SCOPE (ip);
96 #endif
97         break;
98       }
99 #endif /* ENABLE_IPV6 */
100     default:
101       abort ();
102     }
103 }
104
105 /* Get the data of SA, specifically the IP address and the port.  If
106    you're not interested in one or the other information, pass NULL as
107    the pointer.  */
108
109 void
110 sockaddr_get_data (const struct sockaddr *sa, ip_address *ip, int *port)
111 {
112   switch (sa->sa_family)
113     {
114     case AF_INET:
115       {
116         struct sockaddr_in *sin = (struct sockaddr_in *)sa;
117         if (ip)
118           {
119             ip->type = IPV4_ADDRESS;
120             ADDRESS_IPV4_IN_ADDR (ip) = sin->sin_addr;
121           }
122         if (port)
123           *port = ntohs (sin->sin_port);
124         break;
125       }
126 #ifdef ENABLE_IPV6
127     case AF_INET6:
128       {
129         struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
130         if (ip)
131           {
132             ip->type = IPV6_ADDRESS;
133             ADDRESS_IPV6_IN6_ADDR (ip) = sin6->sin6_addr;
134 #ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
135             ADDRESS_IPV6_SCOPE (ip) = sin6->sin6_scope_id;
136 #endif
137           }
138         if (port)
139           *port = ntohs (sin6->sin6_port);
140         break;
141       }
142 #endif
143     default:
144       abort ();
145     }
146 }
147
148 /* Return the size of the sockaddr structure depending on its
149    family.  */
150
151 static socklen_t
152 sockaddr_size (const struct sockaddr *sa)
153 {
154   switch (sa->sa_family)
155     {
156     case AF_INET:
157       return sizeof (struct sockaddr_in);
158 #ifdef ENABLE_IPV6
159     case AF_INET6:
160       return sizeof (struct sockaddr_in6);
161 #endif
162     default:
163       abort ();
164       return 0;                 /* so the compiler shuts up. */
165     }
166 }
167 \f
168 static int
169 resolve_bind_address (const char *host, struct sockaddr *sa, int flags)
170 {
171   struct address_list *al;
172
173   /* #### Shouldn't we do this only once?  opt.bind_address won't
174      change during a Wget run!  */
175
176   al = lookup_host (host, flags | LH_SILENT | LH_PASSIVE);
177   if (al == NULL)
178     {
179       /* #### We should print the error message here. */
180       logprintf (LOG_NOTQUIET,
181                  _("%s: unable to resolve bind address `%s'; disabling bind.\n"),
182                  exec_name, opt.bind_address);
183       return 0;
184     }
185
186   /* Pick the first address in the list and use it as bind address.
187      Perhaps we should try multiple addresses, but I don't think
188      that's necessary in practice.  */
189   sockaddr_set_data (sa, address_list_address_at (al, 0), 0);
190   address_list_release (al);
191   return 1;
192 }
193 \f
194 struct cwt_context {
195   int fd;
196   const struct sockaddr *addr;
197   socklen_t addrlen;
198   int result;
199 };
200
201 static void
202 connect_with_timeout_callback (void *arg)
203 {
204   struct cwt_context *ctx = (struct cwt_context *)arg;
205   ctx->result = connect (ctx->fd, ctx->addr, ctx->addrlen);
206 }
207
208 /* Like connect, but specifies a timeout.  If connecting takes longer
209    than TIMEOUT seconds, -1 is returned and errno is set to
210    ETIMEDOUT.  */
211
212 static int
213 connect_with_timeout (int fd, const struct sockaddr *addr, socklen_t addrlen,
214                       double timeout)
215 {
216   struct cwt_context ctx;
217   ctx.fd = fd;
218   ctx.addr = addr;
219   ctx.addrlen = addrlen;
220
221   if (run_with_timeout (timeout, connect_with_timeout_callback, &ctx))
222     {
223       errno = ETIMEDOUT;
224       return -1;
225     }
226   if (ctx.result == -1 && errno == EINTR)
227     errno = ETIMEDOUT;
228   return ctx.result;
229 }
230 \f
231 /* Connect to a remote endpoint whose IP address is known.  */
232
233 int
234 connect_to_ip (const ip_address *ip, int port, const char *print)
235 {
236   struct sockaddr_storage ss;
237   struct sockaddr *sa = (struct sockaddr *)&ss;
238   int sock = -1;
239
240   /* If PRINT is non-NULL, print the "Connecting to..." line, with
241      PRINT being the host name we're connecting to.  */
242   if (print)
243     {
244       const char *txt_addr = pretty_print_address (ip);
245       if (print && 0 != strcmp (print, txt_addr))
246         logprintf (LOG_VERBOSE,
247                    _("Connecting to %s|%s|:%d... "), print, txt_addr, port);
248       else
249         logprintf (LOG_VERBOSE, _("Connecting to %s:%d... "), txt_addr, port);
250     }
251
252   /* Store the sockaddr info to SA.  */
253   sockaddr_set_data (sa, ip, port);
254
255   /* Create the socket of the family appropriate for the address.  */
256   sock = socket (sa->sa_family, SOCK_STREAM, 0);
257   if (sock < 0)
258     goto err;
259
260   /* For very small rate limits, set the buffer size (and hence,
261      hopefully, the kernel's TCP window size) to the per-second limit.
262      That way we should never have to sleep for more than 1s between
263      network reads.  */
264   if (opt.limit_rate && opt.limit_rate < 8192)
265     {
266       int bufsize = opt.limit_rate;
267       if (bufsize < 512)
268         bufsize = 512;          /* avoid pathologically small values */
269 #ifdef SO_RCVBUF
270       setsockopt (sock, SOL_SOCKET, SO_RCVBUF,
271                   (void *)&bufsize, (socklen_t)sizeof (bufsize));
272 #endif
273       /* When we add limit_rate support for writing, which is useful
274          for POST, we should also set SO_SNDBUF here.  */
275     }
276
277   if (opt.bind_address)
278     {
279       /* Bind the client side of the socket to the requested
280          address.  */
281       struct sockaddr_storage bind_ss;
282       struct sockaddr *bind_sa = (struct sockaddr *)&bind_ss;
283       if (resolve_bind_address (opt.bind_address, bind_sa, 0))
284         {
285           if (bind (sock, bind_sa, sockaddr_size (bind_sa)) < 0)
286             goto err;
287         }
288     }
289
290   /* Connect the socket to the remote endpoint.  */
291   if (connect_with_timeout (sock, sa, sockaddr_size (sa),
292                             opt.connect_timeout) < 0)
293     goto err;
294
295   /* Success. */
296   assert (sock >= 0);
297   if (print)
298     logprintf (LOG_VERBOSE, _("connected.\n"));
299   DEBUGP (("Created socket %d.\n", sock));
300   return sock;
301
302  err:
303   {
304     /* Protect errno from possible modifications by close and
305        logprintf.  */
306     int save_errno = errno;
307     if (sock >= 0)
308       xclose (sock);
309     if (print)
310       logprintf (LOG_VERBOSE, "failed: %s.\n", strerror (errno));
311     errno = save_errno;
312     return -1;
313   }
314 }
315
316 /* Connect to a remote endpoint specified by host name.  */
317
318 int
319 connect_to_host (const char *host, int port)
320 {
321   int i, start, end;
322   struct address_list *al;
323   int sock = -1;
324
325  again:
326   al = lookup_host (host, 0);
327   if (!al)
328     return E_HOST;
329
330   address_list_get_bounds (al, &start, &end);
331   for (i = start; i < end; i++)
332     {
333       const ip_address *ip = address_list_address_at (al, i);
334       sock = connect_to_ip (ip, port, host);
335       if (sock >= 0)
336         /* Success. */
337         break;
338
339       address_list_set_faulty (al, i);
340
341       /* The attempt to connect has failed.  Continue with the loop
342          and try next address. */
343     }
344   address_list_release (al);
345
346   if (sock < 0 && address_list_cached_p (al))
347     {
348       /* We were unable to connect to any address in a list we've
349          obtained from cache.  There is a possibility that the host is
350          under dynamic DNS and has changed its address.  Resolve it
351          again.  */
352       forget_host_lookup (host);
353       goto again;
354     }
355
356   return sock;
357 }
358
359 int
360 test_socket_open (int sock)
361 {
362 #ifdef HAVE_SELECT
363   fd_set check_set;
364   struct timeval to;
365
366   /* Check if we still have a valid (non-EOF) connection.  From Andrew
367    * Maholski's code in the Unix Socket FAQ.  */
368
369   FD_ZERO (&check_set);
370   FD_SET (sock, &check_set);
371
372   /* Wait one microsecond */
373   to.tv_sec = 0;
374   to.tv_usec = 1;
375
376   /* If we get a timeout, then that means still connected */
377   if (select (sock + 1, &check_set, NULL, NULL, &to) == 0)
378     {
379       /* Connection is valid (not EOF), so continue */
380       return 1;
381     }
382   else
383     return 0;
384 #else
385   /* Without select, it's hard to know for sure. */
386   return 1;
387 #endif
388 }
389
390 /* Create a socket and bind it to PORT locally.  Calling accept() on
391    such a socket waits for and accepts incoming TCP connections.  The
392    resulting socket is stored to LOCAL_SOCK.  */
393
394 uerr_t
395 bindport (const ip_address *bind_address, int *port, int *local_sock)
396 {
397   int sock;
398   int family = AF_INET;
399   struct sockaddr_storage ss;
400   struct sockaddr *sa = (struct sockaddr *)&ss;
401
402   /* For setting options with setsockopt. */
403   int setopt_val = 1;
404   void *setopt_ptr = (void *)&setopt_val;
405   socklen_t setopt_size = sizeof (setopt_val);
406
407 #ifdef ENABLE_IPV6
408   if (bind_address->type == IPV6_ADDRESS) 
409     family = AF_INET6;
410 #endif
411
412   if ((sock = socket (family, SOCK_STREAM, 0)) < 0)
413     return CONSOCKERR;
414
415 #ifdef SO_REUSEADDR
416   setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, setopt_ptr, setopt_size);
417 #endif
418
419 #ifdef ENABLE_IPV6
420 # ifdef HAVE_IPV6_V6ONLY
421   if (family == AF_INET6)
422     setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, setopt_ptr, setopt_size);
423 # endif
424 #endif
425
426   xzero (ss);
427   sockaddr_set_data (sa, bind_address, *port);
428   if (bind (sock, sa, sockaddr_size (sa)) < 0)
429     {
430       xclose (sock);
431       return BINDERR;
432     }
433   DEBUGP (("Local socket fd %d bound.\n", sock));
434   if (!*port)
435     {
436       socklen_t sa_len = sockaddr_size (sa);
437       if (getsockname (sock, sa, &sa_len) < 0)
438         {
439           xclose (sock);
440           return CONPORTERR;
441         }
442       sockaddr_get_data (sa, NULL, port);
443       DEBUGP (("binding to address %s using port %i.\n", 
444                pretty_print_address (bind_address), *port));
445     }
446   if (listen (sock, 1) < 0)
447     {
448       xclose (sock);
449       return LISTENERR;
450     }
451   *local_sock = sock;
452   return BINDOK;
453 }
454
455 #ifdef HAVE_SELECT
456 /* Wait for file descriptor FD to be readable or writable or both,
457    timing out after MAXTIME seconds.  Returns 1 if FD is available, 0
458    for timeout and -1 for error.  The argument WAIT_FOR can be a
459    combination of WAIT_READ and WAIT_WRITE.
460
461    This is a mere convenience wrapper around the select call, and
462    should be taken as such.  */
463
464 int
465 select_fd (int fd, double maxtime, int wait_for)
466 {
467   fd_set fdset;
468   fd_set *rd = NULL, *wr = NULL;
469   struct timeval tmout;
470   int result;
471
472   FD_ZERO (&fdset);
473   FD_SET (fd, &fdset);
474   if (wait_for & WAIT_FOR_READ)
475     rd = &fdset;
476   if (wait_for & WAIT_FOR_WRITE)
477     wr = &fdset;
478
479   tmout.tv_sec = (long) maxtime;
480   tmout.tv_usec = 1000000L * (maxtime - (long) maxtime);
481
482   do
483     result = select (fd + 1, rd, wr, NULL, &tmout);
484   while (result < 0 && errno == EINTR);
485
486   return result;
487 }
488 #endif /* HAVE_SELECT */
489
490 /* Accept a connection on LOCAL_SOCK, and store the new socket to
491    *SOCK.  It blocks the caller until a connection is established.  If
492    no connection is established for opt.connect_timeout seconds, the
493    function exits with an error status.  */
494
495 uerr_t
496 acceptport (int local_sock, int *sock)
497 {
498   struct sockaddr_storage ss;
499   struct sockaddr *sa = (struct sockaddr *)&ss;
500   socklen_t addrlen = sizeof (ss);
501
502 #ifdef HAVE_SELECT
503   if (opt.connect_timeout)
504     if (select_fd (local_sock, opt.connect_timeout, WAIT_FOR_READ) <= 0)
505       return ACCEPTERR;
506 #endif
507   if ((*sock = accept (local_sock, sa, &addrlen)) < 0)
508     return ACCEPTERR;
509   DEBUGP (("Created socket fd %d.\n", *sock));
510   return ACCEPTOK;
511 }
512
513 /* Return the local IP address associated with the connection on FD.  */
514
515 int
516 conaddr (int fd, ip_address *ip)
517 {
518   struct sockaddr_storage storage;
519   struct sockaddr *sockaddr = (struct sockaddr *)&storage;
520   socklen_t addrlen = sizeof (storage);
521
522   if (getsockname (fd, sockaddr, &addrlen) < 0)
523     return 0;
524
525   switch (sockaddr->sa_family)
526     {
527 #ifdef ENABLE_IPV6
528     case AF_INET6:
529       {
530         struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&storage;
531         ip->type = IPV6_ADDRESS;
532         ADDRESS_IPV6_IN6_ADDR (ip) = sa6->sin6_addr;
533 #ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
534         ADDRESS_IPV6_SCOPE (ip) = sa6->sin6_scope_id;
535 #endif
536         DEBUGP (("conaddr is: %s\n", pretty_print_address (ip)));
537         return 1;
538       }
539 #endif
540     case AF_INET:
541       {
542         struct sockaddr_in *sa = (struct sockaddr_in *)&storage;
543         ip->type = IPV4_ADDRESS;
544         ADDRESS_IPV4_IN_ADDR (ip) = sa->sin_addr;
545         DEBUGP (("conaddr is: %s\n", pretty_print_address (ip)));
546         return 1;
547       }
548     default:
549       abort ();
550     }
551
552   return 0;
553 }
554 \f
555 /* Basic socket operations, mostly EINTR wrappers.  */
556
557 #ifdef WINDOWS
558 # define read(fd, buf, cnt) recv (fd, buf, cnt, 0)
559 # define write(fd, buf, cnt) send (fd, buf, cnt, 0)
560 # define close(fd) closesocket (fd)
561 #endif
562
563 #ifdef __BEOS__
564 # define read(fd, buf, cnt) recv (fd, buf, cnt, 0)
565 # define write(fd, buf, cnt) send (fd, buf, cnt, 0)
566 #endif
567
568 static int
569 sock_read (int fd, char *buf, int bufsize)
570 {
571   int res;
572   do
573     res = read (fd, buf, bufsize);
574   while (res == -1 && errno == EINTR);
575   return res;
576 }
577
578 static int
579 sock_write (int fd, char *buf, int bufsize)
580 {
581   int res = 0;
582   do
583     res = write (fd, buf, bufsize);
584   while (res == -1 && errno == EINTR);
585   return res;
586 }
587
588 static int
589 sock_poll (int fd, double timeout, int wait_for)
590 {
591 #ifdef HAVE_SELECT
592   return select_fd (fd, timeout, wait_for);
593 #else
594   return 1;
595 #endif
596 }
597
598 static void
599 sock_close (int fd)
600 {
601   close (fd);
602   DEBUGP (("Closed fd %d\n", fd));
603 }
604 #undef read
605 #undef write
606 #undef close
607 \f
608 /* Reading and writing from the network.  We build around the socket
609    (file descriptor) API, but support "extended" operations for things
610    that are not mere file descriptors under the hood, such as SSL
611    sockets.
612
613    That way the user code can call xread(fd, ...) and we'll run read
614    or SSL_read or whatever is necessary.  */
615
616 static struct hash_table *extended_map;
617 static int extended_map_modified_tick;
618
619 struct extended_info {
620   xreader_t reader;
621   xwriter_t writer;
622   xpoller_t poller;
623   xcloser_t closer;
624   void *ctx;
625 };
626
627 void
628 register_extended (int fd, xreader_t reader, xwriter_t writer,
629                    xpoller_t poller, xcloser_t closer, void *ctx)
630 {
631   struct extended_info *info;
632
633   /* The file descriptor must be non-negative to be registered.
634      Negative values are ignored by xclose(), and -1 cannot be used as
635      hash key.  */
636   assert (fd >= 0);
637
638   info = xnew (struct extended_info);
639   info->reader = reader;
640   info->writer = writer;
641   info->poller = poller;
642   info->closer = closer;
643   info->ctx = ctx;
644   if (!extended_map)
645     extended_map = hash_table_new (0, NULL, NULL);
646   hash_table_put (extended_map, (void *) fd, info);
647   ++extended_map_modified_tick;
648 }
649
650 /* When xread/xwrite are called multiple times in a loop, they should
651    remember the INFO pointer instead of fetching it every time.  It is
652    not enough to compare FD to LAST_FD because FD might have been
653    closed and reopened.  modified_tick ensures that changes to
654    extended_map will not be unnoticed.
655
656    This is a macro because we want the static storage variables to be
657    per-function.  */
658
659 #define LAZY_RETRIEVE_INFO(info) do {                                   \
660   static struct extended_info *last_info;                               \
661   static int last_fd = -1, last_tick;                                   \
662   if (!extended_map)                                                    \
663     info = NULL;                                                        \
664   else if (last_fd == fd && last_tick == extended_map_modified_tick)    \
665     info = last_info;                                                   \
666   else                                                                  \
667     {                                                                   \
668       info = hash_table_get (extended_map, (void *) fd);                \
669       last_fd = fd;                                                     \
670       last_info = info;                                                 \
671       last_tick = extended_map_modified_tick;                           \
672     }                                                                   \
673 } while (0)
674
675 /* Read no more than BUFSIZE bytes of data from FD, storing them to
676    BUF.  If TIMEOUT is non-zero, the operation aborts if no data is
677    received after that many seconds.  If TIMEOUT is -1, the value of
678    opt.timeout is used for TIMEOUT.  */
679
680 int
681 xread (int fd, char *buf, int bufsize, double timeout)
682 {
683   struct extended_info *info;
684   LAZY_RETRIEVE_INFO (info);
685   if (timeout == -1)
686     timeout = opt.read_timeout;
687   if (timeout)
688     {
689       int test;
690       if (info && info->poller)
691         test = info->poller (fd, timeout, WAIT_FOR_READ, info->ctx);
692       else
693         test = sock_poll (fd, timeout, WAIT_FOR_READ);
694       if (test == 0)
695         errno = ETIMEDOUT;
696       if (test <= 0)
697         return -1;
698     }
699   if (info && info->reader)
700     return info->reader (fd, buf, bufsize, info->ctx);
701   else
702     return sock_read (fd, buf, bufsize);
703 }
704
705 /* Write the entire contents of BUF to FD.  If TIMEOUT is non-zero,
706    the operation aborts if no data is received after that many
707    seconds.  If TIMEOUT is -1, the value of opt.timeout is used for
708    TIMEOUT.  */
709
710 int
711 xwrite (int fd, char *buf, int bufsize, double timeout)
712 {
713   int res;
714   struct extended_info *info;
715   LAZY_RETRIEVE_INFO (info);
716   if (timeout == -1)
717     timeout = opt.read_timeout;
718
719   /* `write' may write less than LEN bytes, thus the loop keeps trying
720      it until all was written, or an error occurred.  */
721   res = 0;
722   while (bufsize > 0)
723     {
724       if (timeout)
725         {
726           int test;
727           if (info && info->poller)
728             test = info->poller (fd, timeout, WAIT_FOR_WRITE, info->ctx);
729           else
730             test = sock_poll (fd, timeout, WAIT_FOR_WRITE);
731           if (test == 0)
732             errno = ETIMEDOUT;
733           if (test <= 0)
734             return -1;
735         }
736       if (info && info->writer)
737         res = info->writer (fd, buf, bufsize, info->ctx);
738       else
739         res = sock_write (fd, buf, bufsize);
740       if (res <= 0)
741         break;
742       buf += res;
743       bufsize -= res;
744     }
745   return res;
746 }
747
748 /* Close the file descriptor FD.  */
749
750 void
751 xclose (int fd)
752 {
753   struct extended_info *info;
754   if (fd < 0)
755     return;
756
757   /* We don't need to be extra-fast here, so save some code by
758      avoiding LAZY_RETRIEVE_INFO. */
759   info = NULL;
760   if (extended_map)
761     info = hash_table_get (extended_map, (void *) fd);
762
763   if (info && info->closer)
764     info->closer (fd, info->ctx);
765   else
766     sock_close (fd);
767
768   if (info)
769     {
770       hash_table_remove (extended_map, (void *) fd);
771       xfree (info);
772       ++extended_map_modified_tick;
773     }
774 }