# Behavior traces — curl/curl

> Canonical page: https://codevetter.com/unpack/curl/behavior-traces

# Behavior traces — curl/curl

Scanned commit c362f034692e05ecb4d33edb740ba64c63a20add (2026-09-22). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

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()`. — [src/tool_main.c#L152-L213](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_main.c#L152-L213) [src/tool_main.c#L110-L138](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_main.c#L110-L138)
- 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()`. — [src/tool_operate.c#L2431-L2556](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L2431-L2556)
- 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`. — [src/tool_operate.c#L2091-L2189](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L2091-L2189) [src/tool_operate.c#L2308-L2352](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L2308-L2352)
- `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. — [src/tool_operate.c#L1365-L1425](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L1365-L1425) [src/tool_operate.c#L1516-L1552](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L1516-L1552)
- `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). — [src/tool_operate.c#L246-L318](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L246-L318)
- 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()`. — [src/tool_operate.c#L1991-L2089](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L1991-L2089) [src/tool_operate.c#L1903-L1968](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L1903-L1968)
- `--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. — [src/tool_operate.c#L1919-L1926](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L1919-L1926) [src/tool_operate.c#L2043-L2055](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L2043-L2055)
- 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()`. — [lib/easy.c#L773-L845](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/easy.c#L773-L845)
- `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`. — [lib/easy.c#L720-L754](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/easy.c#L720-L754)
- 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`. — [lib/multi.c#L2714-L2890](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/multi.c#L2714-L2890)
- 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. — [lib/multi.c#L152-L200](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/multi.c#L152-L200)
- 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`. — [lib/cf-setup.c#L296-L358](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/cf-setup.c#L296-L358) [lib/cf-setup.c#L228-L292](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/cf-setup.c#L228-L292)
- 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. — [lib/multi.c#L2735-L2750](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/multi.c#L2735-L2750) [lib/cshutdn.c](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/cshutdn.c)
- 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()`. — [src/tool_operate.c#L2513-L2522](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L2513-L2522) [src/tool_ssls.c](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_ssls.c) [lib/cookie.c#L1674-L1695](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/cookie.c#L1674-L1695)
- 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`. — [src/tool_operate.c#L2355-L2391](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L2355-L2391)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
