X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=src%2Fconnect.c;h=ab1fd08e7a03e7430136622db3ab54e3b274ac05;hb=4d7c5e087b2bc82c9f503dff003916d1047903ce;hp=35e4cfdc097e7f9abb18e48a8a4fc8a55d72934e;hpb=3792aa7058caf96e1ed03a1e51792ee706cb0626;p=wget diff --git a/src/connect.c b/src/connect.c index 35e4cfdc..ab1fd08e 100644 --- a/src/connect.c +++ b/src/connect.c @@ -1,11 +1,11 @@ /* Establishing and handling network connections. - Copyright (C) 1995, 1996, 1997, 2001, 2002 Free Software Foundation, Inc. + Copyright (C) 1996-2006 Free Software Foundation, Inc. This file is part of GNU Wget. GNU Wget is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or +the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Wget is distributed in the hope that it will be useful, @@ -14,8 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Wget; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +along with Wget. If not, see . In addition, as a special exception, the Free Software Foundation gives permission to link the code of its release of Wget with the @@ -31,7 +30,6 @@ so, delete this exception statement from your version. */ #include #include -#include #ifdef HAVE_UNISTD_H # include #endif @@ -47,11 +45,7 @@ so, delete this exception statement from your version. */ #endif /* not WINDOWS */ #include -#ifdef HAVE_STRING_H -# include -#else -# include -#endif /* HAVE_STRING_H */ +#include #ifdef HAVE_SYS_SELECT_H # include #endif /* HAVE_SYS_SELECT_H */ @@ -62,11 +56,15 @@ so, delete this exception statement from your version. */ #include "connect.h" #include "hash.h" -#ifndef errno -extern int errno; -#endif +/* Define sockaddr_storage where unavailable (presumably on IPv4-only + hosts). */ + +#ifndef ENABLE_IPV6 +# ifndef HAVE_STRUCT_SOCKADDR_STORAGE +# define sockaddr_storage sockaddr_in +# endif +#endif /* ENABLE_IPV6 */ - /* Fill SA as per the data in IP and PORT. SA shoult point to struct sockaddr_storage if ENABLE_IPV6 is defined, to struct sockaddr_in otherwise. */ @@ -74,25 +72,27 @@ extern int errno; static void sockaddr_set_data (struct sockaddr *sa, const ip_address *ip, int port) { - switch (ip->type) + switch (ip->family) { - case IPV4_ADDRESS: + case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *)sa; + xzero (*sin); sin->sin_family = AF_INET; sin->sin_port = htons (port); - sin->sin_addr = ADDRESS_IPV4_IN_ADDR (ip); + sin->sin_addr = ip->data.d4; break; } #ifdef ENABLE_IPV6 - case IPV6_ADDRESS: + case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + xzero (*sin6); sin6->sin6_family = AF_INET6; sin6->sin6_port = htons (port); - sin6->sin6_addr = ADDRESS_IPV6_IN6_ADDR (ip); + sin6->sin6_addr = ip->data.d6; #ifdef HAVE_SOCKADDR_IN6_SCOPE_ID - sin6->sin6_scope_id = ADDRESS_IPV6_SCOPE (ip); + sin6->sin6_scope_id = ip->ipv6_scope; #endif break; } @@ -116,8 +116,8 @@ sockaddr_get_data (const struct sockaddr *sa, ip_address *ip, int *port) struct sockaddr_in *sin = (struct sockaddr_in *)sa; if (ip) { - ip->type = IPV4_ADDRESS; - ADDRESS_IPV4_IN_ADDR (ip) = sin->sin_addr; + ip->family = AF_INET; + ip->data.d4 = sin->sin_addr; } if (port) *port = ntohs (sin->sin_port); @@ -129,10 +129,10 @@ sockaddr_get_data (const struct sockaddr *sa, ip_address *ip, int *port) struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; if (ip) { - ip->type = IPV6_ADDRESS; - ADDRESS_IPV6_IN6_ADDR (ip) = sin6->sin6_addr; + ip->family = AF_INET6; + ip->data.d6 = sin6->sin6_addr; #ifdef HAVE_SOCKADDR_IN6_SCOPE_ID - ADDRESS_IPV6_SCOPE (ip) = sin6->sin6_scope_id; + ip->ipv6_scope = sin6->sin6_scope_id; #endif } if (port) @@ -161,18 +161,23 @@ sockaddr_size (const struct sockaddr *sa) #endif default: abort (); - return 0; /* so the compiler shuts up. */ } } -static int +/* Resolve the bind address specified via --bind-address and store it + to SA. The resolved value is stored in a static variable and + reused after the first invocation of this function. + + Returns true on success, false on failure. */ + +static bool resolve_bind_address (struct sockaddr *sa) { struct address_list *al; /* Make sure this is called only once. opt.bind_address doesn't change during a Wget run. */ - static int called, should_bind; + static bool called, should_bind; static ip_address ip; if (called) { @@ -180,7 +185,7 @@ resolve_bind_address (struct sockaddr *sa) sockaddr_set_data (sa, &ip, 0); return should_bind; } - called = 1; + called = true; al = lookup_host (opt.bind_address, LH_BIND | LH_SILENT); if (!al) @@ -189,8 +194,8 @@ resolve_bind_address (struct sockaddr *sa) logprintf (LOG_NOTQUIET, _("%s: unable to resolve bind address `%s'; disabling bind.\n"), exec_name, opt.bind_address); - should_bind = 0; - return 0; + should_bind = false; + return false; } /* Pick the first address in the list and use it as bind address. @@ -200,8 +205,8 @@ resolve_bind_address (struct sockaddr *sa) address_list_release (al); sockaddr_set_data (sa, &ip, 0); - should_bind = 1; - return 1; + should_bind = true; + return true; } struct cwt_context { @@ -251,16 +256,16 @@ connect_to_ip (const ip_address *ip, int port, const char *print) { struct sockaddr_storage ss; struct sockaddr *sa = (struct sockaddr *)&ss; - int sock = -1; + int sock; /* If PRINT is non-NULL, print the "Connecting to..." line, with PRINT being the host name we're connecting to. */ if (print) { - const char *txt_addr = pretty_print_address (ip); + const char *txt_addr = print_address (ip); if (print && 0 != strcmp (print, txt_addr)) - logprintf (LOG_VERBOSE, - _("Connecting to %s|%s|:%d... "), print, txt_addr, port); + logprintf (LOG_VERBOSE, _("Connecting to %s|%s|:%d... "), + escnonprint (print), txt_addr, port); else logprintf (LOG_VERBOSE, _("Connecting to %s:%d... "), txt_addr, port); } @@ -273,6 +278,17 @@ connect_to_ip (const ip_address *ip, int port, const char *print) if (sock < 0) goto err; +#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY) + if (opt.ipv6_only) { + int on = 1; + /* In case of error, we will go on anyway... */ + int err = setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof (on)); + IF_DEBUG + if (err < 0) + DEBUGP (("Failed setting IPV6_V6ONLY: %s", strerror (errno))); + } +#endif + /* For very small rate limits, set the buffer size (and hence, hopefully, the kernel's TCP window size) to the per-second limit. That way we should never have to sleep for more than 1s between @@ -321,9 +337,9 @@ connect_to_ip (const ip_address *ip, int port, const char *print) logprintf. */ int save_errno = errno; if (sock >= 0) - xclose (sock); + fd_close (sock); if (print) - logprintf (LOG_VERBOSE, "failed: %s.\n", strerror (errno)); + logprintf (LOG_VERBOSE, _("failed: %s.\n"), strerror (errno)); errno = save_errno; return -1; } @@ -398,7 +414,6 @@ int bind_local (const ip_address *bind_address, int *port) { int sock; - int family = AF_INET; struct sockaddr_storage ss; struct sockaddr *sa = (struct sockaddr *)&ss; @@ -407,12 +422,7 @@ bind_local (const ip_address *bind_address, int *port) void *setopt_ptr = (void *)&setopt_val; socklen_t setopt_size = sizeof (setopt_val); -#ifdef ENABLE_IPV6 - if (bind_address->type == IPV6_ADDRESS) - family = AF_INET6; -#endif - - sock = socket (family, SOCK_STREAM, 0); + sock = socket (bind_address->family, SOCK_STREAM, 0); if (sock < 0) return -1; @@ -420,18 +430,11 @@ bind_local (const ip_address *bind_address, int *port) setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, setopt_ptr, setopt_size); #endif -#ifdef ENABLE_IPV6 -# ifdef HAVE_IPV6_V6ONLY - if (family == AF_INET6) - setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, setopt_ptr, setopt_size); -# endif -#endif - xzero (ss); sockaddr_set_data (sa, bind_address, *port); if (bind (sock, sa, sockaddr_size (sa)) < 0) { - xclose (sock); + fd_close (sock); return -1; } DEBUGP (("Local socket fd %d bound.\n", sock)); @@ -439,23 +442,23 @@ bind_local (const ip_address *bind_address, int *port) /* If *PORT is 0, find out which port we've bound to. */ if (*port == 0) { - socklen_t sa_len = sockaddr_size (sa); - if (getsockname (sock, sa, &sa_len) < 0) + socklen_t addrlen = sockaddr_size (sa); + if (getsockname (sock, sa, &addrlen) < 0) { /* If we can't find out the socket's local address ("name"), something is seriously wrong with the socket, and it's unusable for us anyway because we must know the chosen port. */ - xclose (sock); + fd_close (sock); return -1; } sockaddr_get_data (sa, NULL, port); - DEBUGP (("binding to address %s using port %i.\n", - pretty_print_address (bind_address), *port)); + DEBUGP (("binding to address %s using port %i.\n", + print_address (bind_address), *port)); } if (listen (sock, 1) < 0) { - xclose (sock); + fd_close (sock); return -1; } return sock; @@ -496,13 +499,13 @@ accept_connection (int local_sock) } /* Get the IP address associated with the connection on FD and store - it to IP. Return 1 on success, 0 otherwise. + it to IP. Return true on success, false otherwise. If ENDPOINT is ENDPOINT_LOCAL, it returns the address of the local (client) side of the socket. Else if ENDPOINT is ENDPOINT_PEER, it returns the address of the remote (peer's) side of the socket. */ -int +bool socket_ip_address (int sock, ip_address *ip, int endpoint) { struct sockaddr_storage storage; @@ -517,50 +520,47 @@ socket_ip_address (int sock, ip_address *ip, int endpoint) else abort (); if (ret < 0) - return 0; + return false; + ip->family = sockaddr->sa_family; switch (sockaddr->sa_family) { #ifdef ENABLE_IPV6 case AF_INET6: { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&storage; - ip->type = IPV6_ADDRESS; - ADDRESS_IPV6_IN6_ADDR (ip) = sa6->sin6_addr; + ip->data.d6 = sa6->sin6_addr; #ifdef HAVE_SOCKADDR_IN6_SCOPE_ID - ADDRESS_IPV6_SCOPE (ip) = sa6->sin6_scope_id; + ip->ipv6_scope = sa6->sin6_scope_id; #endif - DEBUGP (("conaddr is: %s\n", pretty_print_address (ip))); - return 1; + DEBUGP (("conaddr is: %s\n", print_address (ip))); + return true; } #endif case AF_INET: { struct sockaddr_in *sa = (struct sockaddr_in *)&storage; - ip->type = IPV4_ADDRESS; - ADDRESS_IPV4_IN_ADDR (ip) = sa->sin_addr; - DEBUGP (("conaddr is: %s\n", pretty_print_address (ip))); - return 1; + ip->data.d4 = sa->sin_addr; + DEBUGP (("conaddr is: %s\n", print_address (ip))); + return true; } default: abort (); } - - return 0; } -/* Return non-zero if the error from the connect code can be - considered retryable. Wget normally retries after errors, but the - exception are the "unsupported protocol" type errors (possible on - IPv4/IPv6 dual family systems) and "connection refused". */ +/* Return true if the error from the connect code can be considered + retryable. Wget normally retries after errors, but the exception + are the "unsupported protocol" type errors (possible on IPv4/IPv6 + dual family systems) and "connection refused". */ -int +bool retryable_socket_connect_error (int err) { /* Have to guard against some of these values not being defined. Cannot use a switch statement because some of the values might be equal. */ - if (0 + if (false #ifdef EAFNOSUPPORT || err == EAFNOSUPPORT #endif @@ -580,45 +580,22 @@ retryable_socket_connect_error (int err) instead of EAFNOSUPPORT and such. */ || err == EINVAL ) - return 0; - - if (err == ECONNREFUSED && !opt.retry_connrefused) - return 0; - - return 1; -} + return false; -#ifdef ENABLE_IPV6 -# ifndef HAVE_GETADDRINFO_AI_ADDRCONFIG - -/* Return non-zero if the INET6 socket family is supported on the - system. - - This doesn't guarantee that we're able to connect to IPv6 hosts, - but it's better than nothing. It is only used on systems where - getaddrinfo doesn't support AI_ADDRCONFIG. (See lookup_host.) */ + if (!opt.retry_connrefused) + if (err == ECONNREFUSED +#ifdef ENETUNREACH + || err == ENETUNREACH /* network is unreachable */ +#endif +#ifdef EHOSTUNREACH + || err == EHOSTUNREACH /* host is unreachable */ +#endif + ) + return false; -int -socket_has_inet6 (void) -{ - static int supported = -1; - if (supported == -1) - { - int sock = socket (AF_INET6, SOCK_STREAM, 0); - if (sock < 0) - supported = 0; - else - { - xclose (sock); - supported = 1; - } - } - return supported; + return true; } -# endif/* not HAVE_GETADDRINFO_AI_ADDRCONFIG */ -#endif /* ENABLE_IPV6 */ - /* Wait for a single descriptor to become available, timing out after MAXTIME seconds. Returns 1 if FD is available, 0 for timeout and -1 for error. The argument WAIT_FOR can be a combination of @@ -631,7 +608,6 @@ socket_has_inet6 (void) int select_fd (int fd, double maxtime, int wait_for) { -#ifdef HAVE_SELECT fd_set fdset; fd_set *rd = NULL, *wr = NULL; struct timeval tmout; @@ -645,30 +621,29 @@ select_fd (int fd, double maxtime, int wait_for) wr = &fdset; tmout.tv_sec = (long) maxtime; - tmout.tv_usec = 1000000L * (maxtime - (long) maxtime); + tmout.tv_usec = 1000000 * (maxtime - (long) maxtime); do result = select (fd + 1, rd, wr, NULL, &tmout); while (result < 0 && errno == EINTR); return result; +} -#else /* not HAVE_SELECT */ - - /* If select() unavailable, just return 1. In most usages in Wget, - this is the appropriate response -- "if we can't poll, go ahead - with the blocking operation". If a specific part of code needs - different behavior, it can use #ifdef HAVE_SELECT to test whether - polling really occurs. */ - return 1; +/* Return true iff the connection to the remote site established + through SOCK is still open. -#endif /* not HAVE_SELECT */ -} + Specifically, this function returns true if SOCK is not ready for + reading. This is because, when the connection closes, the socket + is ready for reading because EOF is about to be delivered. A side + effect of this method is that sockets that have pending data are + considered non-open. This is actually a good thing for callers of + this function, where such pending data can only be unwanted + leftover from a previous request. */ -int +bool test_socket_open (int sock) { -#ifdef HAVE_SELECT fd_set check_set; struct timeval to; @@ -682,18 +657,13 @@ test_socket_open (int sock) to.tv_sec = 0; to.tv_usec = 1; - /* If we get a timeout, then that means still connected */ if (select (sock + 1, &check_set, NULL, NULL, &to) == 0) - { - /* Connection is valid (not EOF), so continue */ - return 1; - } + /* We got a timeout, it means we're still connected. */ + return true; else - return 0; -#else - /* Without select, it's hard to know for sure. */ - return 1; -#endif + /* Read now would not wait, it means we have either pending data + or EOF/error. */ + return false; } /* Basic socket operations, mostly EINTR wrappers. */ @@ -722,7 +692,7 @@ sock_read (int fd, char *buf, int bufsize) static int sock_write (int fd, char *buf, int bufsize) { - int res = 0; + int res; do res = write (fd, buf, bufsize); while (res == -1 && errno == EINTR); @@ -735,6 +705,16 @@ sock_poll (int fd, double timeout, int wait_for) return select_fd (fd, timeout, wait_for); } +static int +sock_peek (int fd, char *buf, int bufsize) +{ + int res; + do + res = recv (fd, buf, bufsize, MSG_PEEK); + while (res == -1 && errno == EINTR); + return res; +} + static void sock_close (int fd) { @@ -750,17 +730,14 @@ sock_close (int fd) that are not mere file descriptors under the hood, such as SSL sockets. - That way the user code can call xread(fd, ...) and we'll run read + That way the user code can call fd_read(fd, ...) and we'll run read or SSL_read or whatever is necessary. */ static struct hash_table *transport_map; -static int transport_map_modified_tick; +static unsigned int transport_map_modified_tick; struct transport_info { - xreader_t reader; - xwriter_t writer; - xpoller_t poller; - xcloser_t closer; + struct transport_implementation *imp; void *ctx; }; @@ -772,29 +749,36 @@ struct transport_info { call getpeername, etc. */ void -register_transport (int fd, xreader_t reader, xwriter_t writer, - xpoller_t poller, xcloser_t closer, void *ctx) +fd_register_transport (int fd, struct transport_implementation *imp, void *ctx) { struct transport_info *info; /* The file descriptor must be non-negative to be registered. - Negative values are ignored by xclose(), and -1 cannot be used as + Negative values are ignored by fd_close(), and -1 cannot be used as hash key. */ assert (fd >= 0); info = xnew (struct transport_info); - info->reader = reader; - info->writer = writer; - info->poller = poller; - info->closer = closer; + info->imp = imp; info->ctx = ctx; if (!transport_map) transport_map = hash_table_new (0, NULL, NULL); - hash_table_put (transport_map, (void *) fd, info); + hash_table_put (transport_map, (void *)(intptr_t) fd, info); ++transport_map_modified_tick; } -/* When xread/xwrite are called multiple times in a loop, they should +/* Return context of the transport registered with + fd_register_transport. This assumes fd_register_transport was + previously called on FD. */ + +void * +fd_transport_context (int fd) +{ + struct transport_info *info = hash_table_get (transport_map, (void *)(intptr_t) fd); + return info->ctx; +} + +/* When fd_read/fd_write are called multiple times in a loop, they should remember the INFO pointer instead of fetching it every time. It is not enough to compare FD to LAST_FD because FD might have been closed and reopened. modified_tick ensures that changes to @@ -805,83 +789,105 @@ register_transport (int fd, xreader_t reader, xwriter_t writer, #define LAZY_RETRIEVE_INFO(info) do { \ static struct transport_info *last_info; \ - static int last_fd = -1, last_tick; \ + static int last_fd = -1; \ + static unsigned int last_tick; \ if (!transport_map) \ info = NULL; \ else if (last_fd == fd && last_tick == transport_map_modified_tick) \ info = last_info; \ else \ { \ - info = hash_table_get (transport_map, (void *) fd); \ + info = hash_table_get (transport_map, (void *)(intptr_t) fd); \ last_fd = fd; \ last_info = info; \ last_tick = transport_map_modified_tick; \ } \ } while (0) -/* Read no more than BUFSIZE bytes of data from FD, storing them to - BUF. If TIMEOUT is non-zero, the operation aborts if no data is - received after that many seconds. If TIMEOUT is -1, the value of - opt.timeout is used for TIMEOUT. */ - -int -xread (int fd, char *buf, int bufsize, double timeout) +static bool +poll_internal (int fd, struct transport_info *info, int wf, double timeout) { - struct transport_info *info; - LAZY_RETRIEVE_INFO (info); if (timeout == -1) timeout = opt.read_timeout; if (timeout) { int test; - if (info && info->poller) - test = info->poller (fd, timeout, WAIT_FOR_READ, info->ctx); + if (info && info->imp->poller) + test = info->imp->poller (fd, timeout, wf, info->ctx); else - test = sock_poll (fd, timeout, WAIT_FOR_READ); + test = sock_poll (fd, timeout, wf); if (test == 0) errno = ETIMEDOUT; if (test <= 0) - return -1; + return false; } - if (info && info->reader) - return info->reader (fd, buf, bufsize, info->ctx); + return true; +} + +/* Read no more than BUFSIZE bytes of data from FD, storing them to + BUF. If TIMEOUT is non-zero, the operation aborts if no data is + received after that many seconds. If TIMEOUT is -1, the value of + opt.timeout is used for TIMEOUT. */ + +int +fd_read (int fd, char *buf, int bufsize, double timeout) +{ + struct transport_info *info; + LAZY_RETRIEVE_INFO (info); + if (!poll_internal (fd, info, WAIT_FOR_READ, timeout)) + return -1; + if (info && info->imp->reader) + return info->imp->reader (fd, buf, bufsize, info->ctx); else return sock_read (fd, buf, bufsize); } +/* Like fd_read, except it provides a "preview" of the data that will + be read by subsequent calls to fd_read. Specifically, it copies no + more than BUFSIZE bytes of the currently available data to BUF and + returns the number of bytes copied. Return values and timeout + semantics are the same as those of fd_read. + + CAVEAT: Do not assume that the first subsequent call to fd_read + will retrieve the same amount of data. Reading can return more or + less data, depending on the TCP implementation and other + circumstances. However, barring an error, it can be expected that + all the peeked data will eventually be read by fd_read. */ + +int +fd_peek (int fd, char *buf, int bufsize, double timeout) +{ + struct transport_info *info; + LAZY_RETRIEVE_INFO (info); + if (!poll_internal (fd, info, WAIT_FOR_READ, timeout)) + return -1; + if (info && info->imp->peeker) + return info->imp->peeker (fd, buf, bufsize, info->ctx); + else + return sock_peek (fd, buf, bufsize); +} + /* Write the entire contents of BUF to FD. If TIMEOUT is non-zero, the operation aborts if no data is received after that many seconds. If TIMEOUT is -1, the value of opt.timeout is used for TIMEOUT. */ int -xwrite (int fd, char *buf, int bufsize, double timeout) +fd_write (int fd, char *buf, int bufsize, double timeout) { int res; struct transport_info *info; LAZY_RETRIEVE_INFO (info); - if (timeout == -1) - timeout = opt.read_timeout; /* `write' may write less than LEN bytes, thus the loop keeps trying it until all was written, or an error occurred. */ res = 0; while (bufsize > 0) { - if (timeout) - { - int test; - if (info && info->poller) - test = info->poller (fd, timeout, WAIT_FOR_WRITE, info->ctx); - else - test = sock_poll (fd, timeout, WAIT_FOR_WRITE); - if (test == 0) - errno = ETIMEDOUT; - if (test <= 0) - return -1; - } - if (info && info->writer) - res = info->writer (fd, buf, bufsize, info->ctx); + if (!poll_internal (fd, info, WAIT_FOR_WRITE, timeout)) + return -1; + if (info && info->imp->writer) + res = info->imp->writer (fd, buf, bufsize, info->ctx); else res = sock_write (fd, buf, bufsize); if (res <= 0) @@ -892,29 +898,58 @@ xwrite (int fd, char *buf, int bufsize, double timeout) return res; } +/* Report the most recent error(s) on FD. This should only be called + after fd_* functions, such as fd_read and fd_write, and only if + they return a negative result. For errors coming from other calls + such as setsockopt or fopen, strerror should continue to be + used. + + If the transport doesn't support error messages or doesn't supply + one, strerror(errno) is returned. The returned error message + should not be used after fd_close has been called. */ + +const char * +fd_errstr (int fd) +{ + /* Don't bother with LAZY_RETRIEVE_INFO, as this will only be called + in case of error, never in a tight loop. */ + struct transport_info *info = NULL; + if (transport_map) + info = hash_table_get (transport_map, (void *)(intptr_t) fd); + + if (info && info->imp->errstr) + { + const char *err = info->imp->errstr (fd, info->ctx); + if (err) + return err; + /* else, fall through and print the system error. */ + } + return strerror (errno); +} + /* Close the file descriptor FD. */ void -xclose (int fd) +fd_close (int fd) { struct transport_info *info; if (fd < 0) return; - /* Don't use LAZY_RETRIEVE_INFO because xclose() is only called once + /* Don't use LAZY_RETRIEVE_INFO because fd_close() is only called once per socket, so that particular optimization wouldn't work. */ info = NULL; if (transport_map) - info = hash_table_get (transport_map, (void *) fd); + info = hash_table_get (transport_map, (void *)(intptr_t) fd); - if (info && info->closer) - info->closer (fd, info->ctx); + if (info && info->imp->closer) + info->imp->closer (fd, info->ctx); else sock_close (fd); if (info) { - hash_table_remove (transport_map, (void *) fd); + hash_table_remove (transport_map, (void *)(intptr_t) fd); xfree (info); ++transport_map_modified_tick; }