/* ** 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 #include #include #include #include #include #include #include #include #include #include #include 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); }