1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 2012 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al. 9 * 10 * This software is licensed as described in the file COPYING, which 11 * you should have received as part of this distribution. The terms 12 * are also available at http://curl.haxx.se/docs/copyright.html. 13 * 14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 15 * copies of the Software, and permit persons to whom the Software is 16 * furnished to do so, under the terms of the COPYING file. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 ***************************************************************************/ 22#include "test.h" 23 24#ifdef HAVE_LIMITS_H 25#include <limits.h> 26#endif 27#include <assert.h> 28 29#include "testutil.h" 30#include "warnless.h" 31#include "memdebug.h" 32 33#define TEST_HANG_TIMEOUT 5 * 1000 34#define MAX_EASY_HANDLES 3 35 36static CURL *easy[MAX_EASY_HANDLES]; 37static curl_socket_t sockets[MAX_EASY_HANDLES]; 38static int res = 0; 39 40static size_t callback(char* ptr, size_t size, size_t nmemb, void* data) 41{ 42 ssize_t idx = ((CURL **) data) - easy; 43 curl_socket_t sock; 44 long longdata; 45 CURLcode code; 46 47 const size_t failure = (size * nmemb) ? 0 : 1; 48 49 char *output = malloc(size * nmemb + 1); 50 if (!output) { 51 fprintf(stderr, "output, malloc() failed\n"); 52 res = TEST_ERR_MAJOR_BAD; 53 return failure; 54 } 55 56 memcpy(output, ptr, size * nmemb); 57 output[size * nmemb] = '\0'; 58 fprintf(stdout, "%s", output); 59 free(output); 60 61 /* Get socket being used for this easy handle, otherwise CURL_SOCKET_BAD */ 62 code = curl_easy_getinfo(easy[idx], CURLINFO_LASTSOCKET, &longdata); 63 if (CURLE_OK != code) { 64 fprintf(stderr, "%s:%d curl_easy_getinfo() failed, " 65 "with code %d (%s)\n", 66 __FILE__, __LINE__, (int)code, curl_easy_strerror(code)); 67 res = TEST_ERR_MAJOR_BAD; 68 return failure; 69 } 70 if (longdata == -1L) 71 sock = CURL_SOCKET_BAD; 72 else 73 sock = (curl_socket_t)longdata; 74 75 if (sock != CURL_SOCKET_BAD) { 76 /* Track relationship between this easy handle and the socket. */ 77 if (sockets[idx] == CURL_SOCKET_BAD) { 78 /* An easy handle without previous socket, record the socket. */ 79 sockets[idx] = sock; 80 } 81 else if (sock != sockets[idx]) { 82 /* An easy handle with a socket different to previously 83 tracked one, log and fail right away. Known bug #37. */ 84 fprintf(stderr, "Handle %d started on socket %d and moved to %d\n", 85 curlx_sztosi(idx), (int)sockets[idx], (int)sock); 86 res = TEST_ERR_MAJOR_BAD; 87 return failure; 88 } 89 } 90 return size * nmemb; 91} 92 93enum HandleState { 94 ReadyForNewHandle, 95 NeedSocketForNewHandle, 96 NoMoreHandles 97}; 98 99int test(char *url) 100{ 101 CURLM *multi = NULL; 102 int running; 103 int i, j; 104 int num_handles = 0; 105 enum HandleState state = ReadyForNewHandle; 106 char* full_url = malloc(strlen(url) + 4 + 1); 107 108 start_test_timing(); 109 110 if (!full_url) { 111 fprintf(stderr, "Not enough memory for full url\n"); 112 return TEST_ERR_MAJOR_BAD; 113 } 114 115 for (i = 0; i < MAX_EASY_HANDLES; ++i) { 116 easy[i] = NULL; 117 sockets[i] = CURL_SOCKET_BAD; 118 } 119 120 res_global_init(CURL_GLOBAL_ALL); 121 if(res) { 122 free(full_url); 123 return res; 124 } 125 126 multi_init(multi); 127 128#ifdef USE_PIPELINING 129 multi_setopt(multi, CURLMOPT_PIPELINING, 1L); 130 multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, 5L); 131 multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, 10L); 132#endif 133 134 for(;;) { 135 struct timeval interval; 136 fd_set fdread; 137 fd_set fdwrite; 138 fd_set fdexcep; 139 long timeout = -99; 140 int maxfd = -99; 141 bool found_new_socket = FALSE; 142 143 /* Start a new handle if we aren't at the max */ 144 if (state == ReadyForNewHandle) { 145 easy_init(easy[num_handles]); 146 147 if (num_handles % 3 == 2) { 148 sprintf(full_url, "%s0200", url); 149 easy_setopt(easy[num_handles], CURLOPT_HTTPAUTH, CURLAUTH_NTLM); 150 } else { 151 sprintf(full_url, "%s0100", url); 152 easy_setopt(easy[num_handles], CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 153 } 154 easy_setopt(easy[num_handles], CURLOPT_FRESH_CONNECT, 1L); 155 easy_setopt(easy[num_handles], CURLOPT_URL, full_url); 156 easy_setopt(easy[num_handles], CURLOPT_VERBOSE, 1L); 157 easy_setopt(easy[num_handles], CURLOPT_HTTPGET, 1L); 158 easy_setopt(easy[num_handles], CURLOPT_USERPWD, "testuser:testpass"); 159 easy_setopt(easy[num_handles], CURLOPT_WRITEFUNCTION, callback); 160 easy_setopt(easy[num_handles], CURLOPT_WRITEDATA, easy + num_handles); 161 easy_setopt(easy[num_handles], CURLOPT_HEADER, 1L); 162 163 multi_add_handle(multi, easy[num_handles]); 164 num_handles += 1; 165 state = NeedSocketForNewHandle; 166 } 167 168 multi_perform(multi, &running); 169 170 abort_on_test_timeout(); 171 172 if(!running && state == NoMoreHandles) 173 break; /* done */ 174 175 FD_ZERO(&fdread); 176 FD_ZERO(&fdwrite); 177 FD_ZERO(&fdexcep); 178 179 multi_fdset(multi, &fdread, &fdwrite, &fdexcep, &maxfd); 180 181 /* At this point, maxfd is guaranteed to be greater or equal than -1. */ 182 183 /* Any socket which is new in fdread is associated with the new handle */ 184 for (i = 0; i <= maxfd; ++i) { 185 bool socket_exists = FALSE; 186 curl_socket_t curfd = (curl_socket_t)i; 187 188 if (!FD_ISSET(curfd, &fdread)) { 189 continue; 190 } 191 192 /* Check if this socket was already detected for an earlier handle (or 193 for this handle, num_handles-1, in the callback */ 194 for (j = 0; j < num_handles; ++j) { 195 if (sockets[j] == curfd) { 196 socket_exists = TRUE; 197 break; 198 } 199 } 200 if (socket_exists) { 201 continue; 202 } 203 204 if (found_new_socket || state != NeedSocketForNewHandle) { 205 fprintf(stderr, "Unexpected new socket\n"); 206 res = TEST_ERR_MAJOR_BAD; 207 goto test_cleanup; 208 } 209 210 /* Now we know the socket is for the most recent handle, num_handles-1 */ 211 if (sockets[num_handles-1] != CURL_SOCKET_BAD) { 212 /* A socket for this handle was already detected in the callback; if it 213 matched socket_exists should be true and we would never get here */ 214 assert(curfd != sockets[num_handles-1]); 215 fprintf(stderr, "Handle %d wrote to socket %d then detected on %d\n", 216 num_handles-1, (int)sockets[num_handles-1], (int)curfd); 217 res = TEST_ERR_MAJOR_BAD; 218 goto test_cleanup; 219 } 220 else { 221 sockets[num_handles-1] = curfd; 222 found_new_socket = TRUE; 223 /* continue to make sure there's only one new handle */ 224 } 225 } 226 227 if (state == NeedSocketForNewHandle) { 228 if(maxfd != -1 && !found_new_socket) { 229 fprintf(stderr, "Warning: socket did not open immediately for new " 230 "handle (trying again)\n"); 231 continue; 232 } 233 state = num_handles < MAX_EASY_HANDLES ? ReadyForNewHandle 234 : NoMoreHandles; 235 } 236 237 multi_timeout(multi, &timeout); 238 239 /* At this point, timeout is guaranteed to be greater or equal than -1. */ 240 241 fprintf(stderr, "%s:%d num_handles %d timeout %ld\n", 242 __FILE__, __LINE__, num_handles, timeout); 243 244 if(timeout != -1L) { 245 int itimeout = (timeout > (long)INT_MAX) ? INT_MAX : (int)timeout; 246 interval.tv_sec = itimeout/1000; 247 interval.tv_usec = (itimeout%1000)*1000; 248 } 249 else { 250 interval.tv_sec = TEST_HANG_TIMEOUT/1000+1; 251 interval.tv_usec = 0; 252 253 /* if there's no timeout and we get here on the last handle, we may 254 already have read the last part of the stream so waiting makes no 255 sense */ 256 if(!running && num_handles == MAX_EASY_HANDLES) { 257 break; 258 } 259 } 260 261 select_test(maxfd+1, &fdread, &fdwrite, &fdexcep, &interval); 262 263 abort_on_test_timeout(); 264 } 265 266test_cleanup: 267 268 /* proper cleanup sequence - type PB */ 269 270 for(i = 0; i < MAX_EASY_HANDLES; i++) { 271 curl_multi_remove_handle(multi, easy[i]); 272 curl_easy_cleanup(easy[i]); 273 } 274 275 curl_multi_cleanup(multi); 276 curl_global_cleanup(); 277 278 free(full_url); 279 280 return res; 281} 282