| /* |
| ** Copyright 2006, The Android Open Source Project |
| ** |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| */ |
| |
| #include <cutils/sockets.h> |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <sys/socket.h> |
| #include <sys/select.h> |
| #include <sys/types.h> |
| #include <netinet/in.h> |
| #include <netdb.h> |
| |
| static int toggle_O_NONBLOCK(int s) { |
| int flags = fcntl(s, F_GETFL); |
| if (flags == -1 || fcntl(s, F_SETFL, flags ^ O_NONBLOCK) == -1) { |
| close(s); |
| return -1; |
| } |
| return s; |
| } |
| |
| // Connect to the given host and port. |
| // 'timeout' is in seconds (0 for no timeout). |
| // Returns a file descriptor or -1 on error. |
| // On error, check *getaddrinfo_error (for use with gai_strerror) first; |
| // if that's 0, use errno instead. |
| int socket_network_client_timeout(const char* host, int port, int type, int timeout, |
| int* getaddrinfo_error) { |
| struct addrinfo hints; |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = type; |
| |
| char port_str[16]; |
| snprintf(port_str, sizeof(port_str), "%d", port); |
| |
| struct addrinfo* addrs; |
| *getaddrinfo_error = getaddrinfo(host, port_str, &hints, &addrs); |
| if (*getaddrinfo_error != 0) { |
| return -1; |
| } |
| |
| int result = -1; |
| for (struct addrinfo* addr = addrs; addr != NULL; addr = addr->ai_next) { |
| // The Mac doesn't have SOCK_NONBLOCK. |
| int s = socket(addr->ai_family, type, addr->ai_protocol); |
| if (s == -1 || toggle_O_NONBLOCK(s) == -1) break; |
| |
| int rc = connect(s, addr->ai_addr, addr->ai_addrlen); |
| if (rc == 0) { |
| result = toggle_O_NONBLOCK(s); |
| break; |
| } else if (rc == -1 && errno != EINPROGRESS) { |
| close(s); |
| continue; |
| } |
| |
| fd_set r_set; |
| FD_ZERO(&r_set); |
| FD_SET(s, &r_set); |
| fd_set w_set = r_set; |
| |
| struct timeval ts; |
| ts.tv_sec = timeout; |
| ts.tv_usec = 0; |
| if ((rc = select(s + 1, &r_set, &w_set, NULL, (timeout != 0) ? &ts : NULL)) == -1) { |
| close(s); |
| break; |
| } |
| if (rc == 0) { // we had a timeout |
| errno = ETIMEDOUT; |
| close(s); |
| break; |
| } |
| |
| int error = 0; |
| socklen_t len = sizeof(error); |
| if (FD_ISSET(s, &r_set) || FD_ISSET(s, &w_set)) { |
| if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { |
| close(s); |
| break; |
| } |
| } else { |
| close(s); |
| break; |
| } |
| |
| if (error) { // check if we had a socket error |
| // TODO: Update the timeout. |
| errno = error; |
| close(s); |
| continue; |
| } |
| |
| result = toggle_O_NONBLOCK(s); |
| break; |
| } |
| |
| freeaddrinfo(addrs); |
| return result; |
| } |
| |
| int socket_network_client(const char* host, int port, int type) { |
| int getaddrinfo_error; |
| return socket_network_client_timeout(host, port, type, 0, &getaddrinfo_error); |
| } |