hx_upload.c (15071B)
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 #include "first.h" 25 26 static int verbose_u = 1; 27 28 struct transfer_u { 29 int idx; 30 CURL *easy; 31 const char *method; 32 char filename[128]; 33 FILE *out; 34 curl_off_t send_total; 35 curl_off_t recv_size; 36 curl_off_t send_size; 37 curl_off_t fail_at; 38 curl_off_t pause_at; 39 curl_off_t abort_at; 40 int started; 41 int paused; 42 int resumed; 43 int done; 44 }; 45 46 static size_t transfer_count_u = 1; 47 static struct transfer_u *transfer_u; 48 static int forbid_reuse_u = 0; 49 50 static struct transfer_u *get_transfer_for_easy_u(CURL *easy) 51 { 52 size_t i; 53 for(i = 0; i < transfer_count_u; ++i) { 54 if(easy == transfer_u[i].easy) 55 return &transfer_u[i]; 56 } 57 return NULL; 58 } 59 60 static size_t my_write_u_cb(char *buf, size_t nitems, size_t buflen, 61 void *userdata) 62 { 63 struct transfer_u *t = userdata; 64 size_t blen = (nitems * buflen); 65 size_t nwritten; 66 67 curl_mfprintf(stderr, "[t-%d] RECV %ld bytes, total=%ld, pause_at=%ld\n", 68 t->idx, (long)blen, (long)t->recv_size, (long)t->pause_at); 69 if(!t->out) { 70 curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data", 71 t->idx); 72 t->out = fopen(t->filename, "wb"); 73 if(!t->out) 74 return 0; 75 } 76 77 nwritten = fwrite(buf, nitems, buflen, t->out); 78 if(nwritten < blen) { 79 curl_mfprintf(stderr, "[t-%d] write failure\n", t->idx); 80 return 0; 81 } 82 t->recv_size += (curl_off_t)nwritten; 83 return (size_t)nwritten; 84 } 85 86 static size_t my_read_cb(char *buf, size_t nitems, size_t buflen, 87 void *userdata) 88 { 89 struct transfer_u *t = userdata; 90 size_t blen = (nitems * buflen); 91 size_t nread; 92 93 if(t->send_total <= t->send_size) 94 nread = 0; 95 else if((t->send_total - t->send_size) < (curl_off_t)blen) 96 nread = (size_t)(t->send_total - t->send_size); 97 else 98 nread = blen; 99 100 curl_mfprintf(stderr, "[t-%d] SEND %ld bytes, total=%ld, pause_at=%ld\n", 101 t->idx, (long)nread, (long)t->send_total, (long)t->pause_at); 102 103 if(!t->resumed && 104 t->send_size < t->pause_at && 105 ((t->send_size + (curl_off_t)blen) >= t->pause_at)) { 106 curl_mfprintf(stderr, "[t-%d] PAUSE\n", t->idx); 107 t->paused = 1; 108 return CURL_READFUNC_PAUSE; 109 } 110 111 memset(buf, 'x', nread); 112 t->send_size += (curl_off_t)nread; 113 if(t->fail_at > 0 && t->send_size >= t->fail_at) { 114 curl_mfprintf(stderr, "[t-%d] ABORT by read callback at %ld bytes\n", 115 t->idx, (long)t->send_size); 116 return CURL_READFUNC_ABORT; 117 } 118 return (size_t)nread; 119 } 120 121 static int my_progress_u_cb(void *userdata, 122 curl_off_t dltotal, curl_off_t dlnow, 123 curl_off_t ultotal, curl_off_t ulnow) 124 { 125 struct transfer_u *t = userdata; 126 (void)ultotal; 127 (void)dlnow; 128 (void)dltotal; 129 if(t->abort_at > 0 && ulnow >= t->abort_at) { 130 curl_mfprintf(stderr, "[t-%d] ABORT by progress_cb at %ld bytes sent\n", 131 t->idx, (long)ulnow); 132 return 1; 133 } 134 return 0; 135 } 136 137 static int setup_hx_upload(CURL *hnd, const char *url, struct transfer_u *t, 138 long http_version, struct curl_slist *host, 139 CURLSH *share, int use_earlydata, 140 int announce_length) 141 { 142 curl_easy_setopt(hnd, CURLOPT_SHARE, share); 143 curl_easy_setopt(hnd, CURLOPT_URL, url); 144 curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, http_version); 145 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); 146 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L); 147 curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, (long)(128 * 1024)); 148 curl_easy_setopt(hnd, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_OBEYCODE); 149 curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_u_cb); 150 curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t); 151 if(use_earlydata) 152 curl_easy_setopt(hnd, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_EARLYDATA); 153 154 if(!t->method || !strcmp("PUT", t->method)) 155 curl_easy_setopt(hnd, CURLOPT_UPLOAD, 1L); 156 else if(!strcmp("POST", t->method)) 157 curl_easy_setopt(hnd, CURLOPT_POST, 1L); 158 else { 159 curl_mfprintf(stderr, "unsupported method '%s'\n", t->method); 160 return 1; 161 } 162 curl_easy_setopt(hnd, CURLOPT_READFUNCTION, my_read_cb); 163 curl_easy_setopt(hnd, CURLOPT_READDATA, t); 164 if(announce_length) 165 curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE, t->send_total); 166 167 curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L); 168 curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_u_cb); 169 curl_easy_setopt(hnd, CURLOPT_XFERINFODATA, t); 170 if(forbid_reuse_u) 171 curl_easy_setopt(hnd, CURLOPT_FORBID_REUSE, 1L); 172 if(host) 173 curl_easy_setopt(hnd, CURLOPT_RESOLVE, host); 174 175 /* please be verbose */ 176 if(verbose_u) { 177 curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); 178 curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, debug_cb); 179 } 180 181 /* wait for pipe connection to confirm */ 182 curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L); 183 184 return 0; /* all is good */ 185 } 186 187 static void usage_hx_upload(const char *msg) 188 { 189 if(msg) 190 curl_mfprintf(stderr, "%s\n", msg); 191 curl_mfprintf(stderr, 192 "usage: [options] url\n" 193 " upload to a url with following options:\n" 194 " -a abort paused transfer\n" 195 " -e use TLS earlydata\n" 196 " -m number max parallel uploads\n" 197 " -n number total uploads\n" 198 " -A number abort transfer after `number` request body bytes\n" 199 " -F number fail reading request body after `number` of bytes\n" 200 " -P number pause transfer after `number` request body bytes\n" 201 " -r <host>:<port>:<addr> resolve information\n" 202 " -S number size to upload\n" 203 " -V http_version (http/1.1, h2, h3) http version to use\n" 204 ); 205 } 206 207 /* 208 * Download a file over HTTP/2, take care of server push. 209 */ 210 static int test_hx_upload(int argc, char *argv[]) 211 { 212 CURLM *multi_handle; 213 CURLSH *share; 214 const char *url; 215 const char *method = "PUT"; 216 size_t i, n, max_parallel = 1; 217 size_t active_transfers; 218 size_t pause_offset = 0; 219 size_t abort_offset = 0; 220 size_t fail_offset = 0; 221 size_t send_total = (128 * 1024); 222 int abort_paused = 0; 223 int reuse_easy = 0; 224 int use_earlydata = 0; 225 int announce_length = 0; 226 struct transfer_u *t; 227 int http_version = CURL_HTTP_VERSION_2_0; 228 struct curl_slist *host = NULL; 229 const char *resolve = NULL; 230 int ch; 231 232 while((ch = cgetopt(argc, argv, "aefhlm:n:A:F:M:P:r:RS:V:")) != -1) { 233 switch(ch) { 234 case 'h': 235 usage_hx_upload(NULL); 236 return 2; 237 case 'a': 238 abort_paused = 1; 239 break; 240 case 'e': 241 use_earlydata = 1; 242 break; 243 case 'f': 244 forbid_reuse_u = 1; 245 break; 246 case 'l': 247 announce_length = 1; 248 break; 249 case 'm': 250 max_parallel = (size_t)strtol(coptarg, NULL, 10); 251 break; 252 case 'n': 253 transfer_count_u = (size_t)strtol(coptarg, NULL, 10); 254 break; 255 case 'A': 256 abort_offset = (size_t)strtol(coptarg, NULL, 10); 257 break; 258 case 'F': 259 fail_offset = (size_t)strtol(coptarg, NULL, 10); 260 break; 261 case 'M': 262 method = coptarg; 263 break; 264 case 'P': 265 pause_offset = (size_t)strtol(coptarg, NULL, 10); 266 break; 267 case 'r': 268 resolve = coptarg; 269 break; 270 case 'R': 271 reuse_easy = 1; 272 break; 273 case 'S': 274 send_total = (size_t)strtol(coptarg, NULL, 10); 275 break; 276 case 'V': { 277 if(!strcmp("http/1.1", coptarg)) 278 http_version = CURL_HTTP_VERSION_1_1; 279 else if(!strcmp("h2", coptarg)) 280 http_version = CURL_HTTP_VERSION_2_0; 281 else if(!strcmp("h3", coptarg)) 282 http_version = CURL_HTTP_VERSION_3ONLY; 283 else { 284 usage_hx_upload("invalid http version"); 285 return 1; 286 } 287 break; 288 } 289 default: 290 usage_hx_upload("invalid option"); 291 return 1; 292 } 293 } 294 argc -= coptind; 295 argv += coptind; 296 297 if(max_parallel > 1 && reuse_easy) { 298 usage_hx_upload("cannot mix -R and -P"); 299 return 2; 300 } 301 302 curl_global_init(CURL_GLOBAL_DEFAULT); 303 curl_global_trace("ids,time,http/2,http/3"); 304 305 if(argc != 1) { 306 usage_hx_upload("not enough arguments"); 307 return 2; 308 } 309 url = argv[0]; 310 311 if(resolve) 312 host = curl_slist_append(NULL, resolve); 313 314 share = curl_share_init(); 315 if(!share) { 316 curl_mfprintf(stderr, "error allocating share\n"); 317 return 1; 318 } 319 curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); 320 curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); 321 curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); 322 curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); 323 curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL); 324 curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS); 325 326 transfer_u = calloc(transfer_count_u, sizeof(*transfer_u)); 327 if(!transfer_u) { 328 curl_mfprintf(stderr, "error allocating transfer structs\n"); 329 return 1; 330 } 331 332 active_transfers = 0; 333 for(i = 0; i < transfer_count_u; ++i) { 334 t = &transfer_u[i]; 335 t->idx = (int)i; 336 t->method = method; 337 t->send_total = (curl_off_t)send_total; 338 t->abort_at = (curl_off_t)abort_offset; 339 t->fail_at = (curl_off_t)fail_offset; 340 t->pause_at = (curl_off_t)pause_offset; 341 } 342 343 if(reuse_easy) { 344 CURL *easy = curl_easy_init(); 345 CURLcode rc = CURLE_OK; 346 if(!easy) { 347 curl_mfprintf(stderr, "failed to init easy handle\n"); 348 return 1; 349 } 350 for(i = 0; i < transfer_count_u; ++i) { 351 t = &transfer_u[i]; 352 t->easy = easy; 353 if(setup_hx_upload(t->easy, url, t, http_version, host, share, 354 use_earlydata, announce_length)) { 355 curl_mfprintf(stderr, "[t-%d] FAILED setup\n", (int)i); 356 return 1; 357 } 358 359 curl_mfprintf(stderr, "[t-%d] STARTING\n", t->idx); 360 rc = curl_easy_perform(easy); 361 curl_mfprintf(stderr, "[t-%d] DONE -> %d\n", t->idx, rc); 362 t->easy = NULL; 363 curl_easy_reset(easy); 364 } 365 curl_easy_cleanup(easy); 366 } 367 else { 368 multi_handle = curl_multi_init(); 369 curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); 370 371 n = (max_parallel < transfer_count_u) ? max_parallel : transfer_count_u; 372 for(i = 0; i < n; ++i) { 373 t = &transfer_u[i]; 374 t->easy = curl_easy_init(); 375 if(!t->easy || setup_hx_upload(t->easy, url, t, http_version, host, 376 share, use_earlydata, announce_length)) { 377 curl_mfprintf(stderr, "[t-%d] FAILED setup\n", (int)i); 378 return 1; 379 } 380 curl_multi_add_handle(multi_handle, t->easy); 381 t->started = 1; 382 ++active_transfers; 383 curl_mfprintf(stderr, "[t-%d] STARTED\n", t->idx); 384 } 385 386 do { 387 int still_running; /* keep number of running handles */ 388 CURLMcode mc = curl_multi_perform(multi_handle, &still_running); 389 struct CURLMsg *m; 390 391 if(still_running) { 392 /* wait for activity, timeout or "nothing" */ 393 mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL); 394 } 395 396 if(mc) 397 break; 398 399 do { 400 int msgq = 0; 401 m = curl_multi_info_read(multi_handle, &msgq); 402 if(m && (m->msg == CURLMSG_DONE)) { 403 CURL *e = m->easy_handle; 404 --active_transfers; 405 curl_multi_remove_handle(multi_handle, e); 406 t = get_transfer_for_easy_u(e); 407 if(t) { 408 long res_status; 409 curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &res_status); 410 t->done = 1; 411 curl_mfprintf(stderr, "[t-%d] FINISHED, result=%d, response=%ld\n", 412 t->idx, m->data.result, res_status); 413 if(use_earlydata) { 414 curl_off_t sent; 415 curl_easy_getinfo(e, CURLINFO_EARLYDATA_SENT_T, &sent); 416 curl_mfprintf(stderr, "[t-%d] EarlyData: %ld\n", t->idx, 417 (long)sent); 418 } 419 } 420 else { 421 curl_easy_cleanup(e); 422 curl_mfprintf(stderr, "unknown FINISHED???\n"); 423 } 424 } 425 426 427 /* nothing happening, maintenance */ 428 if(abort_paused) { 429 /* abort paused transfers */ 430 for(i = 0; i < transfer_count_u; ++i) { 431 t = &transfer_u[i]; 432 if(!t->done && t->paused && t->easy) { 433 curl_multi_remove_handle(multi_handle, t->easy); 434 t->done = 1; 435 active_transfers--; 436 curl_mfprintf(stderr, "[t-%d] ABORTED\n", t->idx); 437 } 438 } 439 } 440 else { 441 /* resume one paused transfer */ 442 for(i = 0; i < transfer_count_u; ++i) { 443 t = &transfer_u[i]; 444 if(!t->done && t->paused) { 445 t->resumed = 1; 446 t->paused = 0; 447 curl_easy_pause(t->easy, CURLPAUSE_CONT); 448 curl_mfprintf(stderr, "[t-%d] RESUMED\n", t->idx); 449 break; 450 } 451 } 452 } 453 454 while(active_transfers < max_parallel) { 455 for(i = 0; i < transfer_count_u; ++i) { 456 t = &transfer_u[i]; 457 if(!t->started) { 458 t->easy = curl_easy_init(); 459 if(!t->easy || setup_hx_upload(t->easy, url, t, http_version, 460 host, share, use_earlydata, 461 announce_length)) { 462 curl_mfprintf(stderr, "[t-%d] FAILED setup\n", (int)i); 463 return 1; 464 } 465 curl_multi_add_handle(multi_handle, t->easy); 466 t->started = 1; 467 ++active_transfers; 468 curl_mfprintf(stderr, "[t-%d] STARTED\n", t->idx); 469 break; 470 } 471 } 472 /* all started */ 473 if(i == transfer_count_u) 474 break; 475 } 476 } while(m); 477 478 } while(active_transfers); /* as long as we have transfers going */ 479 480 curl_multi_cleanup(multi_handle); 481 } 482 483 for(i = 0; i < transfer_count_u; ++i) { 484 t = &transfer_u[i]; 485 if(t->out) { 486 fclose(t->out); 487 t->out = NULL; 488 } 489 if(t->easy) { 490 curl_easy_cleanup(t->easy); 491 t->easy = NULL; 492 } 493 } 494 free(transfer_u); 495 curl_share_cleanup(share); 496 497 return 0; 498 }