lib530.c (11243B)
1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 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 https://curl.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 * SPDX-License-Identifier: curl 22 * 23 ***************************************************************************/ 24 25 /* 26 * The purpose of this test is to make sure that if CURLMOPT_SOCKETFUNCTION or 27 * CURLMOPT_TIMERFUNCTION returns error, the associated transfer should be 28 * aborted correctly. 29 */ 30 31 #include "first.h" 32 33 #include "memdebug.h" 34 35 36 static struct t530_ctx { 37 int socket_calls; 38 int max_socket_calls; 39 int timer_calls; 40 int max_timer_calls; 41 char buf[1024]; 42 } t530_ctx; 43 44 static const char *t530_tag(void) 45 { 46 curl_msnprintf(t530_ctx.buf, sizeof(t530_ctx.buf), 47 "[T530-%d-%d] [%d/%d]", 48 t530_ctx.max_socket_calls, t530_ctx.max_timer_calls, 49 t530_ctx.socket_calls, t530_ctx.timer_calls); 50 return t530_ctx.buf; 51 } 52 53 static void t530_msg(const char *msg) 54 { 55 curl_mfprintf(stderr, "%s %s\n", t530_tag(), msg); 56 } 57 58 59 struct t530_Sockets { 60 curl_socket_t *sockets; 61 int count; /* number of sockets actually stored in array */ 62 int max_count; /* max number of sockets that fit in allocated array */ 63 }; 64 65 struct t530_ReadWriteSockets { 66 struct t530_Sockets read, write; 67 }; 68 69 /** 70 * Remove a file descriptor from a sockets array. 71 */ 72 static void t530_removeFd(struct t530_Sockets *sockets, curl_socket_t fd, 73 int mention) 74 { 75 int i; 76 77 if(mention) 78 curl_mfprintf(stderr, "%s remove socket fd %d\n", t530_tag(), (int)fd); 79 80 for(i = 0; i < sockets->count; ++i) { 81 if(sockets->sockets[i] == fd) { 82 if(i < sockets->count - 1) 83 memmove(&sockets->sockets[i], &sockets->sockets[i + 1], 84 sizeof(curl_socket_t) * (sockets->count - (i + 1))); 85 --sockets->count; 86 } 87 } 88 } 89 90 /** 91 * Add a file descriptor to a sockets array. 92 * Return 0 on success, 1 on error. 93 */ 94 static int t530_addFd(struct t530_Sockets *sockets, curl_socket_t fd, 95 const char *what) 96 { 97 /** 98 * To ensure we only have each file descriptor once, we remove it then add 99 * it again. 100 */ 101 curl_mfprintf(stderr, "%s add socket fd %d for %s\n", 102 t530_tag(), (int)fd, what); 103 t530_removeFd(sockets, fd, 0); 104 /* 105 * Allocate array storage when required. 106 */ 107 if(!sockets->sockets) { 108 sockets->sockets = malloc(sizeof(curl_socket_t) * 20U); 109 if(!sockets->sockets) 110 return 1; 111 sockets->max_count = 20; 112 } 113 else if(sockets->count + 1 > sockets->max_count) { 114 curl_socket_t *ptr = realloc(sockets->sockets, sizeof(curl_socket_t) * 115 (sockets->max_count + 20)); 116 if(!ptr) 117 /* cleanup in test_cleanup */ 118 return 1; 119 sockets->sockets = ptr; 120 sockets->max_count += 20; 121 } 122 /* 123 * Add file descriptor to array. 124 */ 125 sockets->sockets[sockets->count] = fd; 126 ++sockets->count; 127 return 0; 128 } 129 130 /** 131 * Callback invoked by curl to poll reading / writing of a socket. 132 */ 133 static int t530_curlSocketCallback(CURL *easy, curl_socket_t s, int action, 134 void *userp, void *socketp) 135 { 136 struct t530_ReadWriteSockets *sockets = userp; 137 138 (void)easy; /* unused */ 139 (void)socketp; /* unused */ 140 141 t530_ctx.socket_calls++; 142 t530_msg("-> CURLMOPT_SOCKETFUNCTION"); 143 if(t530_ctx.socket_calls == t530_ctx.max_socket_calls) { 144 t530_msg("<- CURLMOPT_SOCKETFUNCTION returns error"); 145 return -1; 146 } 147 148 if(action == CURL_POLL_IN || action == CURL_POLL_INOUT) 149 if(t530_addFd(&sockets->read, s, "read")) 150 return -1; /* bail out */ 151 152 if(action == CURL_POLL_OUT || action == CURL_POLL_INOUT) 153 if(t530_addFd(&sockets->write, s, "write")) 154 return -1; 155 156 if(action == CURL_POLL_REMOVE) { 157 t530_removeFd(&sockets->read, s, 1); 158 t530_removeFd(&sockets->write, s, 0); 159 } 160 161 return 0; 162 } 163 164 /** 165 * Callback invoked by curl to set a timeout. 166 */ 167 static int t530_curlTimerCallback(CURLM *multi, long timeout_ms, void *userp) 168 { 169 struct curltime *timeout = userp; 170 171 (void)multi; /* unused */ 172 t530_ctx.timer_calls++; 173 t530_msg("-> CURLMOPT_TIMERFUNCTION"); 174 if(t530_ctx.timer_calls == t530_ctx.max_timer_calls) { 175 t530_msg("<- CURLMOPT_TIMERFUNCTION returns error"); 176 return -1; 177 } 178 if(timeout_ms != -1) { 179 *timeout = curlx_now(); 180 timeout->tv_usec += (int)timeout_ms * 1000; 181 } 182 else { 183 timeout->tv_sec = -1; 184 } 185 return 0; 186 } 187 188 /** 189 * Check for curl completion. 190 */ 191 static int t530_checkForCompletion(CURLM *curl, int *success) 192 { 193 int result = 0; 194 *success = 0; 195 while(1) { 196 int numMessages; 197 CURLMsg *message = curl_multi_info_read(curl, &numMessages); 198 if(!message) 199 break; 200 if(message->msg == CURLMSG_DONE) { 201 result = 1; 202 if(message->data.result == CURLE_OK) 203 *success = 1; 204 else 205 *success = 0; 206 } 207 else { 208 curl_mfprintf(stderr, "%s got an unexpected message from curl: %i\n", 209 t530_tag(), (int)message->msg); 210 result = 1; 211 *success = 0; 212 } 213 } 214 return result; 215 } 216 217 static int t530_getMicroSecondTimeout(struct curltime *timeout) 218 { 219 struct curltime now; 220 ssize_t result; 221 now = curlx_now(); 222 result = (ssize_t)((timeout->tv_sec - now.tv_sec) * 1000000 + 223 timeout->tv_usec - now.tv_usec); 224 if(result < 0) 225 result = 0; 226 227 return curlx_sztosi(result); 228 } 229 230 /** 231 * Update a fd_set with all of the sockets in use. 232 */ 233 static void t530_updateFdSet(struct t530_Sockets *sockets, fd_set* fdset, 234 curl_socket_t *maxFd) 235 { 236 int i; 237 for(i = 0; i < sockets->count; ++i) { 238 #if defined(__DJGPP__) 239 #pragma GCC diagnostic push 240 #pragma GCC diagnostic ignored "-Warith-conversion" 241 #endif 242 FD_SET(sockets->sockets[i], fdset); 243 #if defined(__DJGPP__) 244 #pragma GCC diagnostic pop 245 #endif 246 if(*maxFd < sockets->sockets[i] + 1) { 247 *maxFd = sockets->sockets[i] + 1; 248 } 249 } 250 } 251 252 static int socket_action(CURLM *curl, curl_socket_t s, int evBitmask, 253 const char *info) 254 { 255 int numhandles = 0; 256 CURLMcode result = curl_multi_socket_action(curl, s, evBitmask, &numhandles); 257 if(result != CURLM_OK) { 258 curl_mfprintf(stderr, "%s Curl error on %s (%i) %s\n", 259 t530_tag(), info, result, curl_multi_strerror(result)); 260 } 261 return (int)result; 262 } 263 264 /** 265 * Invoke curl when a file descriptor is set. 266 */ 267 static int t530_checkFdSet(CURLM *curl, struct t530_Sockets *sockets, 268 fd_set *fdset, int evBitmask, const char *name) 269 { 270 int i; 271 int result = 0; 272 for(i = 0; i < sockets->count; ++i) { 273 if(FD_ISSET(sockets->sockets[i], fdset)) { 274 result = socket_action(curl, sockets->sockets[i], evBitmask, name); 275 if(result) 276 break; 277 } 278 } 279 return result; 280 } 281 282 static CURLcode testone(char *URL, int timer_fail_at, int socket_fail_at) 283 { 284 CURLcode res = CURLE_OK; 285 CURL *curl = NULL; CURLM *m = NULL; 286 struct t530_ReadWriteSockets sockets = {{NULL, 0, 0}, {NULL, 0, 0}}; 287 int success = 0; 288 struct curltime timeout = {0}; 289 timeout.tv_sec = (time_t)-1; 290 291 /* set the limits */ 292 memset(&t530_ctx, 0, sizeof(t530_ctx)); 293 t530_ctx.max_timer_calls = timer_fail_at; 294 t530_ctx.max_socket_calls = socket_fail_at; 295 296 t530_msg("start"); 297 start_test_timing(); 298 299 res_global_init(CURL_GLOBAL_ALL); 300 if(res != CURLE_OK) 301 return res; 302 303 easy_init(curl); 304 305 /* specify target */ 306 easy_setopt(curl, CURLOPT_URL, URL); 307 308 /* go verbose */ 309 easy_setopt(curl, CURLOPT_VERBOSE, 1L); 310 311 multi_init(m); 312 313 multi_setopt(m, CURLMOPT_SOCKETFUNCTION, t530_curlSocketCallback); 314 multi_setopt(m, CURLMOPT_SOCKETDATA, &sockets); 315 316 multi_setopt(m, CURLMOPT_TIMERFUNCTION, t530_curlTimerCallback); 317 multi_setopt(m, CURLMOPT_TIMERDATA, &timeout); 318 319 multi_add_handle(m, curl); 320 321 if(socket_action(m, CURL_SOCKET_TIMEOUT, 0, "timeout")) { 322 res = TEST_ERR_MAJOR_BAD; 323 goto test_cleanup; 324 } 325 326 while(!t530_checkForCompletion(m, &success)) { 327 fd_set readSet, writeSet; 328 curl_socket_t maxFd = 0; 329 struct timeval tv = {0}; 330 tv.tv_sec = 10; 331 332 FD_ZERO(&readSet); 333 FD_ZERO(&writeSet); 334 t530_updateFdSet(&sockets.read, &readSet, &maxFd); 335 t530_updateFdSet(&sockets.write, &writeSet, &maxFd); 336 337 if(timeout.tv_sec != (time_t)-1) { 338 int usTimeout = t530_getMicroSecondTimeout(&timeout); 339 tv.tv_sec = usTimeout / 1000000; 340 tv.tv_usec = usTimeout % 1000000; 341 } 342 else if(maxFd <= 0) { 343 tv.tv_sec = 0; 344 tv.tv_usec = 100000; 345 } 346 347 assert(maxFd); 348 select_test((int)maxFd, &readSet, &writeSet, NULL, &tv); 349 350 /* Check the sockets for reading / writing */ 351 if(t530_checkFdSet(m, &sockets.read, &readSet, CURL_CSELECT_IN, 352 "read")) { 353 res = TEST_ERR_MAJOR_BAD; 354 goto test_cleanup; 355 } 356 if(t530_checkFdSet(m, &sockets.write, &writeSet, CURL_CSELECT_OUT, 357 "write")) { 358 res = TEST_ERR_MAJOR_BAD; 359 goto test_cleanup; 360 } 361 362 if(timeout.tv_sec != (time_t)-1 && 363 t530_getMicroSecondTimeout(&timeout) == 0) { 364 /* Curl's timer has elapsed. */ 365 if(socket_action(m, CURL_SOCKET_TIMEOUT, 0, "timeout")) { 366 res = TEST_ERR_BAD_TIMEOUT; 367 goto test_cleanup; 368 } 369 } 370 371 abort_on_test_timeout(); 372 } 373 374 if(!success) { 375 t530_msg("Error getting file."); 376 res = TEST_ERR_MAJOR_BAD; 377 } 378 379 test_cleanup: 380 381 /* proper cleanup sequence */ 382 t530_msg("cleanup"); 383 curl_multi_remove_handle(m, curl); 384 curl_easy_cleanup(curl); 385 curl_multi_cleanup(m); 386 curl_global_cleanup(); 387 388 /* free local memory */ 389 free(sockets.read.sockets); 390 free(sockets.write.sockets); 391 t530_msg("done"); 392 393 return res; 394 } 395 396 static CURLcode test_lib530(char *URL) 397 { 398 CURLcode rc; 399 /* rerun the same transfer multiple times and make it fail in different 400 callback calls */ 401 rc = testone(URL, 0, 0); /* no callback fails */ 402 if(rc) 403 curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), rc); 404 405 rc = testone(URL, 1, 0); /* fail 1st call to timer callback */ 406 if(!rc) 407 curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), rc); 408 409 rc = testone(URL, 2, 0); /* fail 2nd call to timer callback */ 410 if(!rc) 411 curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), rc); 412 413 rc = testone(URL, 0, 1); /* fail 1st call to socket callback */ 414 if(!rc) 415 curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), rc); 416 417 rc = testone(URL, 0, 2); /* fail 2nd call to socket callback */ 418 if(!rc) 419 curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), rc); 420 421 return CURLE_OK; 422 }