CodeVetter · Repo Unpack · Behavior traces
curl/curl
Three flows explain most of the codebase: tool startup, a single serial transfer, and the multi state machine that both serial and parallel modes ultimately run on. The tool never calls sockets directly — it builds `per_transfer` records, sets options, and calls `curl_easy_perform()` (serial) or feeds a `CURLM` (parallel). Inside libcurl, `curl_easy_perform()` is itself a multi loop, so every transfer walks `MSTATE_INIT` → … → `MSTATE_MSGSENT` in `multi_runsingle()`. Connection establishment is a re-entrant loop inside one filter (`cf_setup_connect`) that appends the next filter and restarts the sub-chain connect until the whole stack is up.
- STARTUP: `main()` → `tool_init_stderr()` → (Windows only) `win32_init()` → `main_checkfds()` → `signal(SIGPIPE, SIG_IGN)` → `memory_tracking_init()` (reads `CURL_MEMDEBUG`/`CURL_MEMLIMIT` on debug builds) → `globalconf_init()` → `operate()` → `globalconf_free()`.
- STARTUP (cont.): inside `operate()` the order is `setlocale(LC_NUMERIC,"C")` → `parseconfig()` for `.curlrc` → `parse_args()` → `easysrc_init()` if `--libcurl` → `curl_share_init()` → `share_setup()` → `tool_ssls_load()` → `get_args()` per OperationConfig → `run_all_transfers()` → `curl_share_cleanup()` → `dumpeasysrc()` → `varcleanup()`.
- PRIMARY ACTION (serial): `run_all_transfers()` → `serial_transfers()` → `create_transfer()` → `transfer_per_config()` → `cacertpaths()` → `single_transfer()` → `create_single()`, then per item `pre_transfer()` → `curl_easy_perform()` → `post_per_transfer()`; a retry sleeps `curlx_wait_ms(delay_ms)` and re-runs the same `per`.
- `create_single()` builds one `per_transfer` by walking `setup_input_file()` → `setup_url_pattern()` → `setup_etag_files()` → `curl_easy_init()` → `add_per_transfer()`, and only then applies output/header/option setup; failure paths must both `curl_easy_cleanup()` and close any opened etag stream.
- `pre_transfer()` is what turns `-T file` into `CURLOPT_INFILESIZE_LARGE`: it opens the upload file with `curlx_open(O_RDONLY|CURL_O_BINARY)`, `fstat`s it, and deliberately skips the size for non-`S_ISREG` inputs (devices, sockets, FIFOs).
- PRIMARY ACTION (parallel): `parallel_transfers()` → `curl_multi_init()` → set `CURLMOPT_QUICK_EXIT`, `CURLMOPT_NOTIFYFUNCTION=mnotify`, `CURLMOPT_MAX_HOST_CONNECTIONS` → `curl_multi_notify_enable(CURLMNOTIFY_INFO_READ)` → `add_parallel_transfers()` → loop { `curl_multi_poll(…,1000,…)`; `curl_multi_perform()`; `progress_meter()` } with completions harvested in `check_finished()`.
- `--fail-early` and fatal errors are implemented as a cooperative abort: `check_finished()` sets `s->wrapitup`, the main loop then sets `per->abort = TRUE` on every added transfer, and the progress callback turns that into `CURLE_ABORTED_BY_CALLBACK` which is rewritten into a clearer errorbuffer message.
- LIBRARY ENTRY: `curl_easy_perform()` → `CURL_EAPI_ENTER` guard → `easy_perform()`, which refuses a handle already in a user multi, detaches any connect-only connection, reuses or creates `data->multi_easy` via `Curl_multi_handle(16,1,3,7,3)`, copies MAXCONNECTS/QUICK_EXIT, `Curl_multi_add_handle()`, `sigpipe_init/apply`, then `easy_transfer()`.
- `easy_transfer()` is the blocking pump: `curl_multi_poll()` → `curl_multi_perform()` → when `still_running` hits 0, `curl_multi_info_read()` yields the CURLcode; a CURLMcode is mapped to `CURLE_OUT_OF_MEMORY` or `CURLE_BAD_FUNCTION_ARGUMENT`.
- STATE MACHINE: `multi_runsingle()` clears the handle's dirty bit, optionally runs `multi_schedule_pending()`, checks `multi_handle_timeout()`, then switches to `multistate_init/setup/connect/connecting/protoconnect/protoconnecting/do/doing/doing_more/did/ratelimiting/performing/done`, looping while any handler returns `CURLM_CALL_MULTI_PERFORM`.
- State transitions run entry hooks from a fixed `state_enter[MSTATE_LAST]` table in `mstate()` — only CONNECT, DID, DONE and COMPLETED have hooks — and every transition is traced via `CURL_TRC_M(data, "-> [%s]")`, which is what `--trace-config multi` surfaces.
- CONNECT: `cf_setup_connect()` is a re-entrant loop that connects the sub-chain, then appends filters in a fixed order — ip_happy (happy eyeballs) → SOCKS → HTTP proxy → HAProxy → origin filters (TLS/ALPN) — restarting at `connect_sub_chain` after each addition until `CF_SETUP_DONE`.
- The multi handle's `admin` easy handle is a special case in `multi_runsingle()`: it consumes internal wakeups, drives `Curl_async_thrdd_multi_process()` for threaded resolution, and runs `Curl_cshutdn_perform()` for graceful connection shutdowns, then returns early.
- PERSISTENCE: at the end of `operate()`, if `--ssl-sessions` and the `ssls_export` feature are present, `tool_ssls_save()` writes the TLS session cache back out; cookie jars are written from libcurl's own cleanup path via `Curl_flush_cookies()`.
- TEARDOWN: `run_all_transfers()` always drains the `transfers` list after the mode-specific loop, calling `post_per_transfer()` + `clean_getout()` + `del_per_transfer()` for each leftover, and restores the saved `global->noprogress`/`global->isatty`.