diff --git a/.github/actions/load-release-versions/action.yml b/.github/actions/load-release-versions/action.yml deleted file mode 100644 index bb79dec..0000000 --- a/.github/actions/load-release-versions/action.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "Load release versions" -description: "Export release/versions.env into the job environment (single pin source)." - -runs: - using: composite - steps: - - name: Load release/versions.env - shell: bash - run: | - set -euo pipefail - file="release/versions.env" - if [[ ! -f "${file}" ]]; then - echo "missing ${file}" >&2 - exit 1 - fi - while IFS= read -r line || [[ -n "${line}" ]]; do - line="${line%%#*}" - line="$(echo "${line}" | xargs)" - if [[ -z "${line}" ]]; then - continue - fi - if [[ "${line}" != *"="* ]]; then - echo "invalid line in ${file}: ${line}" >&2 - exit 1 - fi - echo "${line}" >> "${GITHUB_ENV}" - done < "${file}" diff --git a/.github/actions/load-toolchain-pins/action.yml b/.github/actions/load-toolchain-pins/action.yml new file mode 100644 index 0000000..e929f2a --- /dev/null +++ b/.github/actions/load-toolchain-pins/action.yml @@ -0,0 +1,9 @@ +name: "Load toolchain pins" +description: "Export toolchain-pins.env into the job environment." + +runs: + using: composite + steps: + - name: Load toolchain-pins.env + shell: bash + run: bash scripts/load-toolchain-pins.sh "${GITHUB_ENV}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10c671e..3483253 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,11 +13,11 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Load release versions - uses: ./.github/actions/load-release-versions + - name: Load toolchain pins + uses: ./.github/actions/load-toolchain-pins - - name: Verify release version alignment - run: bash scripts/verify-release-versions.sh + - name: Verify toolchain pin alignment + run: bash scripts/verify-toolchain-pins.sh - name: Install system dependencies run: | @@ -71,8 +71,8 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Load release versions - uses: ./.github/actions/load-release-versions + - name: Load toolchain pins + uses: ./.github/actions/load-toolchain-pins - name: Setup guest toolchain uses: ./.github/actions/setup-guest-toolchain @@ -100,8 +100,8 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Load release versions - uses: ./.github/actions/load-release-versions + - name: Load toolchain pins + uses: ./.github/actions/load-toolchain-pins - name: Setup guest toolchain uses: ./.github/actions/setup-guest-toolchain @@ -128,6 +128,12 @@ jobs: - name: Run rollups E2E tests run: just test-rollups-e2e + # Runs after the e2e step so the canonical machine image is already built; + # exercises the in-process machine_cartesi binding incl. store -> reload -> advance, + # which the Rust harness never loads (its compare passes only load the genesis image). + - name: Watchdog Lua CM e2e + run: just test-watchdog-e2e + watchdog-docker: name: Watchdog Docker image smoke runs-on: ubuntu-latest @@ -138,8 +144,8 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Load release versions - uses: ./.github/actions/load-release-versions + - name: Load toolchain pins + uses: ./.github/actions/load-toolchain-pins - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a6cee3..8509458 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,8 +36,8 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Load release versions - uses: ./.github/actions/load-release-versions + - name: Load toolchain pins + uses: ./.github/actions/load-toolchain-pins - name: Install system dependencies run: | @@ -120,8 +120,8 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Load release versions - uses: ./.github/actions/load-release-versions + - name: Load toolchain pins + uses: ./.github/actions/load-toolchain-pins - name: Setup guest toolchain uses: ./.github/actions/setup-guest-toolchain @@ -178,8 +178,8 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Load release versions - uses: ./.github/actions/load-release-versions + - name: Load toolchain pins + uses: ./.github/actions/load-toolchain-pins - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -198,7 +198,6 @@ jobs: --build-arg "GIT_COMMIT=${GITHUB_SHA}" \ --build-arg "CARTESI_MACHINE_VERSION=${CARTESI_MACHINE_VERSION}" \ --build-arg "CARTESI_MACHINE_DEB_SHA256=${DEB_SHA}" \ - --build-arg "LUA_CURL_UPSTREAM_SHA=${LUA_CURL_UPSTREAM_SHA}" \ -f watchdog/Dockerfile \ -t "${image}" \ . @@ -222,8 +221,8 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Load release versions - uses: ./.github/actions/load-release-versions + - name: Load toolchain pins + uses: ./.github/actions/load-toolchain-pins - name: Download build artifacts uses: actions/download-artifact@v6 diff --git a/.gitignore b/.gitignore index 68566ca..d58ae5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ /target .deps/ -watchdog/third_party/lua-curl/* -!watchdog/third_party/lua-curl/UPSTREAM watchdog-e2e-*/ .env .env.fish diff --git a/docs/watchdog/README.md b/docs/watchdog/README.md index a17f2d6..9559f75 100644 --- a/docs/watchdog/README.md +++ b/docs/watchdog/README.md @@ -28,32 +28,48 @@ just setup && just canonical-build-machine-image && just watchdog-lua-deps # Path A — full smoke (Anvil + sequencer + CM + compare), one command: just test-watchdog-compare-harness -# Path B — two terminals: stack prints WATCHDOG_* exports, then run compare: +# Path B — two terminals: stack prints WATCHDOG_* exports, then init + tick: just devnet-for-watchdog # terminal 1 — leave running # terminal 2: paste exports, then: -WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/main.lua +export WATCHDOG_LUA_ROOT="$(pwd)" +export WATCHDOG_LUA_BIN=lua +export WATCHDOG_LUA_DEPS=.deps/lua +./watchdog/sequencer-watchdog init +./watchdog/sequencer-watchdog tick ``` +The `sequencer-watchdog` wrapper wraps `init`/`tick` with an advisory `flock` +on `$WATCHDOG_STATE_DIR/run.lock`. Production schedulers must also prevent +overlapping ticks +(`flock`, systemd, or Kubernetes `concurrencyPolicy: Forbid`). + Details: **[`getting-started.md`](getting-started.md)**. ## Host dependencies (`watchdog-lua-deps`) -Compare mode and any test that hits HTTP need a native **`lcurl.so`** built into `.deps/lua/`. JSON is pure Lua (no compile step). +The watchdog cycle and any test that hits HTTP need a native **`lcurl.so`** built into `.deps/lua/`. JSON is pure Lua (no compile step). ```bash just watchdog-lua-deps # idempotent; writes .deps/lua/lcurl.so export WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua" ``` -You also need **`cartesi-machine`** on `PATH` (in-process `cartesi` Lua module) and **`lua`** (5.4 recommended). +You also need **`cartesi-machine`** on `PATH` (in-process `cartesi` +Lua module), **`lua`** (5.4 recommended), and a scheduler non-overlap +guard. The release Docker image uses Linux `flock`; Nix also provides the +same CLI on macOS/Linux via `nixpkgs#util-linux`: + +```bash +nix shell nixpkgs#util-linux +``` ### System packages | OS | Packages | |----|----------| -| Debian / Ubuntu / WSL | `libcurl4-openssl-dev` `liblua5.4-dev` `lua5.4` `build-essential` | -| Fedora | `libcurl-devel` `lua-devel` | -| Arch | `curl` `lua` | +| Debian / Ubuntu / WSL | `libcurl4-openssl-dev` `liblua5.4-dev` `lua5.4` `build-essential` `util-linux` | +| Fedora | `libcurl-devel` `lua-devel` `util-linux` | +| Arch | `curl` `lua` `util-linux` | Verify before building: @@ -62,7 +78,7 @@ pkg-config --exists libcurl && echo "libcurl ok" test -f /usr/include/lua5.4/lua.h && echo "lua headers ok" ``` -On Debian/Ubuntu, Lua headers live under **`/usr/include/lua5.4/`**, not `/usr/include/`. The repo script fetches pinned lua-cURLv3 at build time and passes `LUA_INC` accordingly (`scripts/watchdog-lua-deps.sh`). +On Debian/Ubuntu, Lua headers live under **`/usr/include/lua5.4/`**, not `/usr/include/`. lua-cURLv3 is **vendored in-tree** under `watchdog/third_party/lua-curl/src`; `scripts/watchdog-lua-deps.sh` compiles it locally (no build-time download), discovering the Lua headers via `pkg-config` (override with `LUA_INC`). ### Troubleshooting `just watchdog-lua-deps` @@ -72,7 +88,6 @@ On Debian/Ubuntu, Lua headers live under **`/usr/include/lua5.4/`**, not `/usr/i | `install Lua headers` | `sudo apt-get install -y liblua5.4-dev` | | `fatal error: lua.h: No such file or directory` | Install `liblua5.4-dev`. If headers are present but build still fails, ensure you are on a tree where `scripts/watchdog-lua-deps.sh` passes **`LUA_INC`** (not `LUA_INCLUDE_DIR`) to make — see script in repo | | `built lcurl.so but lua cannot load it` | Lua version mismatch: build with the same `lua` you run (`lua -v` vs headers under `lua5.4`) | -| `need curl or wget` | Install `curl` or `wget` to download pinned lua-cURLv3 into `.deps/lua-curl-src/` | CI runs **`just test-watchdog`** (mocked HTTP), the divergence drill script, and watchdog rollups-e2e trials (`watchdog_genesis_compare_test`, non-genesis compare inside `deposit_transfer_withdrawal_test`, `watchdog_non_genesis_divergence_test`) plus a **`watchdog-docker`** image smoke job. Run **`just doctor`** locally before CM-backed work. Full local smoke: `just test-watchdog-compare-harness`. @@ -81,20 +96,24 @@ CI runs **`just test-watchdog`** (mocked HTTP), the divergence drill script, and The implementation lives in `watchdog/` and is intentionally split into small Lua modules: -- `http.lua`: HTTP adapter via pinned **lua-cURLv3** / `lcurl` (`just watchdog-lua-deps`, fetch-at-build). +- `http.lua`: HTTP adapter via **lua-cURLv3** / `lcurl`, vendored in-tree and compiled by `just watchdog-lua-deps` (no build-time download). - `json.lua` / `third_party/json.lua`: pure-Lua JSON (RPC + structured watchdog events). - `jsonrpc.lua`: JSON-RPC request/response validation. -- `l1_reader.lua`: partitioned `eth_getLogs` scanning and strict L1 log ordering. +- `l1_reader.lua`: partitioned `eth_getLogs` scanning, strict L1 log ordering, + and chunk callbacks so each successful provider response can be consumed and + discarded. - `abi.lua`: decoding for the `InputAdded` / `EvmAdvance` envelope. - `machine_runner.lua`: CM driver (`load`, `advance`, `inspect`, `dump`). - `machine_cartesi.lua`: in-process `cartesi` Lua module binding (production path). - `sequencer_reader.lua`: sequencer HTTP client (`GET /finalized_state/inclusion_block`, `GET /finalized_state`). - `compare.lua`: raw byte comparison. -- `checkpoint.lua`: manifest-backed checkpoint persistence. +- `checkpoint.lua`: manifest-backed checkpoint persistence (`head.json` pointer). +- `state.lua`: persisted `config.json` and single-run state lock. - `retry.lua`: bounded retry helper used by the runtime. -- `runner.lua`: one-shot orchestration — cheap `/finalized_state/inclusion_block` - poll, optional full pass (L1 fetch, CM replay, SSZ compare, checkpoint write). -- `main.lua`: compare or advance loop (daemon or `WATCHDOG_ONCE=1`). +- `runner.lua`: one compare cycle — cheap `/finalized_state/inclusion_block` + poll, then (when finalized advanced) L1 fetch, CM replay, SSZ compare, + checkpoint write. +- `main.lua`: dispatches `init` and `tick`; `tick` exits `0`/`1`/`2`. The L1 reader follows the Rust partition strategy from `sequencer/src/l1/partition.rs`: if an RPC provider rejects a large range, the @@ -113,8 +132,8 @@ The sequencer exposes operator-internal snapshot routes (see `sequencer/src/egre - `GET /finalized_state/inclusion_block` — cheap JSON `{ inclusion_block, l2_tx_index }` polled every compare tick. - `GET /finalized_state` — streams the finalized SSZ state file (`application/octet-stream`) with `X-Inclusion-Block` and `X-L2-Tx-Index` headers. -**Idle optimization (compare mode):** when `inclusion_block` has not advanced past the -checkpoint's `safe_block` (the last verified inclusion block), the runner returns +**Idle optimization:** when `inclusion_block` has not advanced past the watchdog +checkpoint's `safe_block`, the tick returns immediately — no `/finalized_state` download, no L1 `eth_getLogs`, no CM load/advance/inspect. The watchdog compares the finalized SSZ bytes with the bytes returned by CM @@ -130,8 +149,10 @@ V1 persists only the resulting Cartesi Machine checkpoint, not the fetched L1 inputs. ```text -checkpoint_dir/ - current.json +state_dir/ + config.json + head.json + run.lock # advisory lock handle; file existence is not lock state checkpoints/ 00000000000001234567/ snapshot/ @@ -139,38 +160,47 @@ checkpoint_dir/ ``` `manifest.json` records `safe_block` (the L1 reference block the CM snapshot -covers — in compare mode this is the finalized `inclusion_block`), timestamp, +covers — the finalized `inclusion_block`), timestamp, and optionally the CM image hash. A new checkpoint directory is written first, -then `current.json` is atomically replaced to point at it. +then `head.json` is atomically replaced to point at it. -When bootstrapping without an existing checkpoint, the operator provides both: +`init` stores the operator-provided bootstrap CM snapshot into this layout. `tick` +requires both `config.json` and `head.json`; it never bootstraps from env. +`WATCHDOG_L1_RPC_URL` is intentionally read at tick time, not persisted in +`config.json`, so operators can rotate RPC endpoints without rewriting watchdog +state. - `WATCHDOG_CM_SNAPSHOT_DIR` - `WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK` -## Modes +## How it runs -The default `WATCHDOG_MODE` is `advance`. In this mode the watchdog does not -poll the sequencer. It: +The watchdog has two subcommands: + +```bash +sequencer-watchdog init # one-time setup: writes config.json + head.json +sequencer-watchdog tick # one compare cycle; schedule this +``` -1. Loads the latest checkpoint, or the bootstrap snapshot directory. -2. Reads the L1 safe block from the RPC (or `WATCHDOG_TARGET_SAFE_BLOCK` when - provided for tests/manual runs). -3. Fetches and decodes `InputAdded` logs for the block range. -4. Feeds the raw InputBox input bytes into the CM adapter. -5. Saves a new snapshot directory and advances `current.json`. +`tick` does one cycle per process, then exits — infra schedules re-runs +(systemd timer / k8s CronJob) and reacts to the exit code. There is no daemon +loop. `sequencer-watchdog` takes a non-blocking `flock` for `init`/`tick`; +host scheduling should provide the same non-overlap guarantee. Each tick: -`WATCHDOG_MODE=compare` polls `/finalized_state/inclusion_block` first; when the -block advances, replays L1 inputs into the CM, inspects with query `state`, and -compares the SSZ report bytes against `GET /finalized_state`. +1. Loads the watchdog checkpoint from `head.json`. +2. Polls `/finalized_state/inclusion_block`. If it has not advanced past a + watchdog checkpoint, exits `0` (idle). Otherwise: +3. Streams and decodes `InputAdded` logs for the new block range. +4. Replays each successful L1 partition into the in-process Cartesi Machine, + then inspects with query `state`. +5. Byte-compares the SSZ report against `GET /finalized_state`; on match writes a + new checkpoint, on mismatch emits a `watchdog_event` and exits `2`. -Useful runtime knobs: +Runtime knobs: -- `WATCHDOG_CM_EXECUTABLE` / `WATCHDOG_CM_WORK_DIR`: compatibility/test knobs. - Production compare uses the in-process `cartesi` Lua module. +- `WATCHDOG_L1_RPC_URL`: current L1 JSON-RPC endpoint for tick. - `WATCHDOG_RETRY_ATTEMPTS`: bounded retry attempts per run, default `3`. - `WATCHDOG_RETRY_DELAY_SEC`: delay between retry attempts, default `5`. -- `WATCHDOG_TARGET_SAFE_BLOCK`: manual/test override for the target safe block. ## Local Tests @@ -178,7 +208,7 @@ Useful runtime knobs: |---------|-------------------| | `just test-watchdog` | Lua unit tests (fake HTTP/RPC/CM; no live chain) | | `just test-watchdog-e2e` | Real CM: advance, inspect; optional live compare if `WATCHDOG_E2E_SEQUENCER_URL` set | -| `just test-watchdog-compare-harness` | **Full E2E**: Anvil + devnet sequencer + `/finalized_state` + CM inspect + Lua compare (`main.lua`) | +| `just test-watchdog-compare-harness` | **Full E2E**: Anvil + devnet sequencer + `/finalized_state` + CM inspect + Lua `init`/`tick` | | `just test-rollups-e2e` | All rollups e2e scenarios; includes watchdog genesis/non-genesis compare plus `watchdog_non_genesis_divergence_test` (needs Sepolia CM image) | | `just test-watchdog-divergence-drill` | Synthetic divergence signal drill (`watchdog_event` + exit `2`) | | `just doctor` | Toolchain sanity: lua, cartesi-machine, lcurl, devnet CM image loadable via `machine_cartesi` | @@ -200,7 +230,8 @@ just test-watchdog ``` Covers raw comparison, golden InputAdded ABI decoding, L1 ordering, recursive -range partitioning, config, checkpoints, advance/compare runner (fakes), and retry behavior. +range partitioning, streamed L1 chunks, config, checkpoints, the compare runner +(fakes), and retry behavior. ### Lua CM end-to-end @@ -211,7 +242,6 @@ just test-watchdog-e2e Scenarios (verbose `step NN/NN` logging): - `prerequisites` — `cartesi-machine` on PATH and machine image present. -- `advance-empty-range` — real CM advance + checkpoint write with zero new inputs. - `cm-inspect-state-query` — real `--cmio-inspect-state` with query `state`. - `machine-cartesi-store-reload-advance` — store checkpoint snapshot, reload, advance again (in-process binding). - `compare-runner-with-sequencer` — skipped unless `WATCHDOG_E2E_SEQUENCER_URL` is set. @@ -228,7 +258,7 @@ just test-watchdog-compare-harness Spawns Anvil + rollups devnet + `sequencer-devnet`, proves CM inspect SSZ at genesis matches `wallet_snapshot::encode(WalletConfig::devnet())` (same as `tests/fixtures/wallet_snapshot_v1_empty.hex` only for Sepolia `default()`), then runs -`watchdog/main.lua` in compare mode. +`sequencer-watchdog init` and `sequencer-watchdog tick`. When `inclusion_block` is unchanged at genesis, the runner skips L1/CM work (idle-cheap); `deposit_transfer_withdrawal_test` drives a gold batch first so compare replays real L1 inputs. **Before first run (or after changing scheduler / SSZ / inspect code):** @@ -263,7 +293,7 @@ cargo run -p rollups-e2e --bin rollups-e2e -- \ ### Staging / operator drills -See [`staging-drills.md`](staging-drills.md) for divergence signal and compare-mode drills. +See [`staging-drills.md`](staging-drills.md) for divergence signal and watchdog tick drills. ## Related sequencer tests diff --git a/docs/watchdog/design-notes.md b/docs/watchdog/design-notes.md new file mode 100644 index 0000000..a85ffc9 --- /dev/null +++ b/docs/watchdog/design-notes.md @@ -0,0 +1,114 @@ +# Watchdog Design Notes + +The watchdog is an independent off-chain safety monitor. It advances a +canonical Cartesi Machine from L1 inputs, inspects the resulting SSZ snapshot, +and byte-compares it with the sequencer's `GET /finalized_state` response at +the same finalized `inclusion_block`. + +Its value is independence: the sequencer serves state derived from its own +safe-acceptance simulation, while the watchdog re-derives the canonical +scheduler result from L1. + +## Current Shape + +The watchdog has one executable with two subcommands: + +```bash +sequencer-watchdog init +sequencer-watchdog tick +``` + +`init` records the watchdog's canonical starting state. `tick` runs one compare +cycle and exits; infra schedules `tick` with a timer or CronJob. Runtime +non-overlap is enforced by `sequencer-watchdog` with a kernel `flock`, and +Kubernetes/systemd deployments should use their native non-overlap guard. + +Each tick: + +1. Loads the watchdog checkpoint selected by `head.json`. +2. Reads `GET /finalized_state/inclusion_block`. +3. Exits cheaply if the finalized block is unchanged. +4. Fetches L1 `InputAdded` logs for the open block range. +5. Advances the CM, inspects state, fetches `GET /finalized_state`, and compares + raw SSZ bytes. +6. Writes a new checkpoint only after a successful compare. + +There is no advance-only mode. Advancing the CM is just an implementation step +inside a compare cycle. + +## Watchdog State + +The watchdog state is canonical from the watchdog's point of view. The +sequencer is what gets verified. + +`init` stores the operator-provided bootstrap CM snapshot into the normal +checkpoint layout. `tick` never bootstraps from env; missing `head.json` is an +operator error. + +`config.json` stores stable deployment identity (`sequencer_url`, +`input_box_address`, `app_address`, retry knobs). `WATCHDOG_L1_RPC_URL` is read +at tick time rather than persisted, because provider URLs and credentials are +operational inputs that may rotate. + +Tradeoff accepted: if the watchdog is initialized while the sequencer is already +serving an incorrect finalized state at the exact same block, and the block does +not advance before the next tick, the unchanged-block skip can delay detection +until a future finalized block. This keeps the runtime model simple. + +Unreadable, malformed, or incomplete watchdog state means stop and let the +operator repair the state directory. + +## Checkpoint Crash Model + +Checkpoint writes use a pointer-swap model: + +1. Store the CM snapshot under `checkpoints//snapshot`. +2. Write `manifest.json` next to it. +3. Write `head.json.tmp`. +4. Rename `head.json.tmp` over `head.json`. +5. Best-effort delete the superseded checkpoint. + +Crash before the rename leaves the previous checkpoint selected. Crash after +the rename leaves the new checkpoint selected and may leave an old directory to +clean up later. The code checks write and close errors for the JSON files, but +it does not currently fsync files or directories; if production needs +power-loss-grade durability, use a small SQLite state store or add explicit +fsync support. + +## State Layout + +```text +state/ + config.json + head.json + run.lock # advisory lock handle in the production container + checkpoints/ + 00000000000000000042/ + manifest.json + snapshot/ +``` + +`config.json` is written by `init` and read by every `tick`. `head.json` is the +small mutable pointer. Checkpoints are block-named directories; after a +successful pointer flip, the superseded checkpoint is pruned best-effort. + +## Memory Notes + +The L1 fetch path consumes successful provider partitions immediately: + +- JSON-RPC still reads one `eth_getLogs` response body at a time; +- each successful partition response is sorted locally; +- logs are decoded into an input chunk; +- the runner advances the CM for that partition and then discards the chunk. + +This preserves the operational assumption that one provider response fits in +memory, while avoiding whole-range `logs` plus whole-range decoded `inputs`. +The Cartesi binding may still queue one partition internally while feeding it to +the machine. + +## Open Questions Before Merge + +- Is the current crash model sufficient for a watchdog sidecar, or do operators + need fsync/SQLite durability? +- If provider responses themselves become too large, add provider pagination or + a smaller fixed scan window in a separate change. diff --git a/docs/watchdog/getting-started.md b/docs/watchdog/getting-started.md index 3516a52..c297bd3 100644 --- a/docs/watchdog/getting-started.md +++ b/docs/watchdog/getting-started.md @@ -16,7 +16,7 @@ Step-by-step guide for running the watchdog alongside a **local** `sequencer-dev 3. [Path A — Full automated smoke](#path-a--full-automated-smoke-recommended-first) 4. [Path B — Interactive (two terminals)](#path-b--interactive-sequencer--watchdog-two-terminals) 5. [Production-like deployments](#production-like-deployments-sepolia--mainnet) -6. [Environment reference](#environment-reference-compare-mode) +6. [Environment reference](#environment-reference) 7. [Troubleshooting](#troubleshooting) 8. [Related commands](#related-commands) @@ -28,7 +28,7 @@ Step-by-step guide for running the watchdog alongside a **local** `sequencer-dev |---------|------| | **Anvil** | Local L1 with Cartesi rollups contracts pre-deployed (`just setup`) | | **sequencer-devnet** | Off-chain sequencer (wallet app, batches, snapshot promotion) | -| **watchdog** (`WATCHDOG_MODE=compare`) | Polls `/finalized_state/inclusion_block`, replays L1 inputs in CM, compares SSZ to `/finalized_state` | +| **watchdog** | Polls `/finalized_state/inclusion_block`, replays L1 inputs in CM, compares SSZ to `/finalized_state` | The sequencer exposes (operator-internal, same HTTP listener today): @@ -51,7 +51,7 @@ From the repo root: Without direnv you need on `PATH`: `anvil`, `lua`, `cartesi-machine`, and a C compiler for `lcurl`. -3. **System packages for watchdog HTTP** — see [`README.md` — Host dependencies](README.md#host-dependencies-watchdog-lua-deps) (Debian/WSL: `libcurl4-openssl-dev`, `liblua5.4-dev`, `lua5.4`, then `just watchdog-lua-deps`). +3. **System packages for watchdog HTTP + scheduling** — see [`README.md` — Host dependencies](README.md#host-dependencies-watchdog-lua-deps) (Debian/WSL: `libcurl4-openssl-dev`, `liblua5.4-dev`, `lua5.4`, `util-linux`, then `just watchdog-lua-deps`; Nix: `nixpkgs#util-linux` provides `flock`). 4. **Cartesi Machine** — `cartesi-machine` on `PATH` so the in-process `cartesi` Lua module loads (ships with Cartesi Machine install / nix shell). @@ -100,7 +100,7 @@ Leave Terminal 1 running until you are done; Ctrl+C stops Anvil and the sequence ### Wait for finalized snapshot -Compare mode needs a **finalized** SSZ dump. Right after boot, the cheap endpoint may return **404** until the sequencer has promoted a snapshot. +The watchdog needs a **finalized** SSZ dump. Right after boot, the cheap endpoint may return **404** until the sequencer has promoted a snapshot. In another shell (use the printed `WATCHDOG_SEQUENCER_URL`): @@ -117,37 +117,27 @@ curl -s -D - "$WATCHDOG_SEQUENCER_URL/finalized_state" -o /tmp/finalized-state.b head -c 32 /tmp/finalized-state.bin | xxd ``` -### Terminal 2 — Watchdog (compare mode) +### Terminal 2 — Watchdog From repo root, after `just watchdog-lua-deps`: ```bash -# Paste exports from Terminal 1, then: -export WATCHDOG_MODE=compare -export WATCHDOG_ONCE=1 # single pass; omit for daemon (poll every 30s) - -WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/main.lua +# Paste exports from Terminal 1, then initialize once and run one tick: +export WATCHDOG_LUA_ROOT="$(pwd)" +export WATCHDOG_LUA_BIN=lua +export WATCHDOG_LUA_DEPS=.deps/lua +./watchdog/sequencer-watchdog init +./watchdog/sequencer-watchdog tick ``` -Success: exit **0**, stderr shows `watchdog_step` lines ending in `compare pass complete`. - -Exit codes from `watchdog/main.lua`: **0** clean, **1** transient failure (RPC/CM/network after retries), **2** deterministic divergence (`watchdog_event` emitted on stderr before exit). - -Daemon mode (production-like loop): +Success: exit **0**. If finalized has advanced, stderr ends in `compare pass complete`; if it has not, the tick exits idle after the cheap poll. -```bash -export WATCHDOG_POLL_INTERVAL_SEC=30 -unset WATCHDOG_ONCE -WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/main.lua -``` +Exit codes from `sequencer-watchdog tick`: **0** clean (or idle — finalized unchanged), **1** transient failure (RPC/CM/network after retries), **2** deterministic divergence (`watchdog_event` emitted on stderr before exit). -When `inclusion_block` has not advanced since the last verified checkpoint, the runner **skips** L1/CM work (idle-cheap). - -### One-shot compare (same stack, no daemon) - -```bash -WATCHDOG_ONCE=1 WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/main.lua -``` +The watchdog tick runs **one cycle per process and exits** — re-run it on a timer/cron for continuous monitoring. When `inclusion_block` has not advanced since the watchdog checkpoint, the cycle **skips** L1/CM work (idle-cheap) and exits 0. +`sequencer-watchdog` takes a non-blocking `flock`; production schedulers should +also prevent overlapping ticks with systemd or Kubernetes CronJob +`concurrencyPolicy: Forbid`. --- @@ -168,21 +158,18 @@ Full operator runbook: **[`operator-deployment.md`](operator-deployment.md)**. --- -## Environment reference (compare mode) +## Environment reference | Variable | Required | Description | |----------|----------|-------------| -| `WATCHDOG_MODE` | yes | `compare` | | `WATCHDOG_SEQUENCER_URL` | yes | e.g. `http://127.0.0.1:54321` | -| `WATCHDOG_L1_RPC_URL` | yes | L1 JSON-RPC | +| `WATCHDOG_L1_RPC_URL` | tick | Current L1 JSON-RPC; not persisted by `init` | | `WATCHDOG_INPUTBOX_ADDRESS` | yes | InputBox contract | | `WATCHDOG_APP_ADDRESS` | yes | Rollup application contract | -| `WATCHDOG_CHECKPOINT_DIR` | yes | CM checkpoint storage | -| `WATCHDOG_CM_SNAPSHOT_DIR` | first run | Genesis CM image dir if no checkpoint yet | +| `WATCHDOG_STATE_DIR` | yes | Persistent watchdog state (`config.json`, `head.json`, checkpoints) | +| `WATCHDOG_CM_SNAPSHOT_DIR` | init | Genesis CM image dir | | `WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK` | with above | Usually `0` on fresh devnet | | `WATCHDOG_LUA_DEPS` | yes | `.deps/lua` after `just watchdog-lua-deps` | -| `WATCHDOG_ONCE` | no | `1` = one pass then exit | -| `WATCHDOG_POLL_INTERVAL_SEC` | no | Default `30` (daemon) | See `watchdog/config.lua` for the full list. @@ -200,7 +187,8 @@ See `watchdog/config.lua` for the full list. | CM inspect ~27 bytes / JSON in error | Stale image (old JSON inspect); rebuild: `just canonical-build-machine-image` | | HTTP 404 on `/finalized_state/inclusion_block` | Sequencer not promoted yet; wait or drive L1 + batches | | `state_mismatch` at genesis | Wrong `WATCHDOG_CM_SNAPSHOT_*` or stale CM image vs sequencer build | -| `inclusion_block_regressed` | Checkpoint ahead of sequencer (reset checkpoint dir or fix bootstrap block) | +| `inclusion_block_regressed` | Watchdog state ahead of sequencer (reset state dir or fix bootstrap block) | +| `flock` lock conflict | Another tick is still running or the scheduler allows overlap. With the container `flock`, a leftover `run.lock` path alone is harmless. | | `could not determine which binary to run` | Use `just test-watchdog-compare-harness` (not bare `cargo run -p rollups-e2e`) | | Harness `87 vs 76` or `27 vs 76` byte mismatch | Stale CM image and/or wrong fixture; see [harness troubleshooting](README.md#troubleshooting-just-test-watchdog-compare-harness) | diff --git a/docs/watchdog/operator-deployment.md b/docs/watchdog/operator-deployment.md index a5a8133..6116eed 100644 --- a/docs/watchdog/operator-deployment.md +++ b/docs/watchdog/operator-deployment.md @@ -48,9 +48,39 @@ curl -sS -o /dev/null -w "%{http_code}\n" "$WATCHDOG_SEQUENCER_URL/finalized_sta ### 2. Watchdog runtime (release bundle or local build) -**Production (recommended):** use the **release bundle** for tag `vX` — same git tag as the sequencer binary and `canonical-machine-image-*-vX.tar.gz`. Load `sequencer-watchdog-vX-linux-.tar.gz` (`docker load`), verify alignment via `release-manifest-vX.json` and `/opt/watchdog/RELEASE.json` inside the image. See [`release/README.md`](../../release/README.md). +**Production (recommended):** use the **release bundle** for tag `vX` — same +git tag as the sequencer binary and `canonical-machine-image-*-vX.tar.gz`. +Load `sequencer-watchdog-vX-linux-.tar.gz` (`docker load`) and verify +alignment via `release-manifest-vX.json` and `/opt/watchdog/RELEASE.json` +inside the image. Toolchain pins live in [`toolchain-pins.env`](../../toolchain-pins.env). -`cartesi-machine` in the watchdog image **must** match `CARTESI_MACHINE_VERSION` in `release/versions.env` (the emulator that built the CM image tarball). Mismatch causes load failures or false `state_mismatch`. +`cartesi-machine` in the watchdog image **must** match +`CARTESI_MACHINE_VERSION` in [`toolchain-pins.env`](../../toolchain-pins.env) +(the emulator that built the CM image tarball). Mismatch causes load failures +or false `state_mismatch`. + +Release image quick run: + +```bash +docker load < sequencer-watchdog-vX-linux-amd64.tar.gz + +docker run --rm \ + -e WATCHDOG_SEQUENCER_URL="https://" \ + -e WATCHDOG_INPUTBOX_ADDRESS="0x..." \ + -e WATCHDOG_APP_ADDRESS="0x..." \ + -e WATCHDOG_STATE_DIR=/watchdog-state \ + -e WATCHDOG_CM_SNAPSHOT_DIR=/cm-bootstrap \ + -e WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK="" \ + -v /var/lib/watchdog/state:/watchdog-state \ + -v /var/lib/watchdog/cm-bootstrap:/cm-bootstrap:ro \ + sequencer-watchdog:vX init + +docker run --rm \ + -e WATCHDOG_L1_RPC_URL="https://" \ + -e WATCHDOG_STATE_DIR=/watchdog-state \ + -v /var/lib/watchdog/state:/watchdog-state \ + sequencer-watchdog:vX tick +``` **Local / dev build:** @@ -61,7 +91,11 @@ just watchdog-lua-deps # .deps/lua/lcurl.so — needs libcurl + Lua dev heade Host packages and build errors: [`README.md` — Host dependencies](README.md#host-dependencies-watchdog-lua-deps). -Requires: `lua`, `cartesi-machine` (in-process `cartesi` Lua module), libcurl + Lua headers. Pin `cartesi-machine` to the same version as your CM bootstrap tarball. +Requires: `lua`, `cartesi-machine` (in-process `cartesi` Lua module), +libcurl + Lua headers, and a scheduler non-overlap guard. The release image +installs Linux `flock` from `util-linux`; for Nix shells, the package is +`nixpkgs#util-linux`. Pin `cartesi-machine` to the same version as your CM +bootstrap tarball. ### 3. Build the CM image for **this chain** @@ -79,48 +113,54 @@ Today `WalletApp::default()` / `WalletConfig::sepolia()` align with Sepolia stag | Variable | Where it comes from | |----------|---------------------| -| `WATCHDOG_MODE` | `compare` | | `WATCHDOG_SEQUENCER_URL` | Ops: internal HTTP base (see network diagram) | -| `WATCHDOG_L1_RPC_URL` | Ops: chain RPC (archive for historical `getLogs`) | +| `WATCHDOG_L1_RPC_URL` | Ops: current chain RPC for `tick` (archive for historical `getLogs`; not persisted by `init`) | | `WATCHDOG_APP_ADDRESS` | This rollup’s Cartesi **application** contract | | `WATCHDOG_INPUTBOX_ADDRESS` | InputBox on that L1 ([Cartesi deployed contracts](https://docs.cartesi.io/cartesi-rollups/2.0/deployment/self-hosted.md)) | -| `WATCHDOG_CHECKPOINT_DIR` | Persistent volume on watchdog host | -| `WATCHDOG_CM_SNAPSHOT_DIR` | Bootstrap CM snapshot (first run only) | +| `WATCHDOG_STATE_DIR` | Persistent volume on watchdog host | +| `WATCHDOG_CM_SNAPSHOT_DIR` | Bootstrap CM snapshot (`init` only) | | `WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK` | L1 block that bootstrap snapshot represents (= finalized `inclusion_block` at bootstrap) | | `WATCHDOG_LUA_DEPS` | `.deps/lua` | -| `WATCHDOG_POLL_INTERVAL_SEC` | `120`–`300` on public L1 (finalized advances slowly) | The sequencer discovers and pins `input_box_address` at startup; use the same values as `SEQ_ETH_RPC_URL` / `SEQ_APP_ADDRESS` configuration. -### 5. Bootstrap checkpoint (first run on a live chain) +### 5. Initialize watchdog state (first run on a live chain) On a long-lived deployment, **`WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK=0` is usually wrong** unless finalized state is still at genesis. Pick one: 1. **Ops hands off** a CM snapshot directory + block number matching current finalized `inclusion_block`, or -2. **Watchdog reuses** `WATCHDOG_CHECKPOINT_DIR` from a prior successful compare on this deployment, or +2. **Watchdog reuses** `WATCHDOG_STATE_DIR` from a prior run on this deployment, or 3. **Replay from genesis** (only for new rollups / low block height — slow). -After bootstrap, the watchdog advances its own checkpoint each successful compare. - -### 6. Run compare - -One shot (smoke): +Run `init` once to store the bootstrap CM snapshot into the watchdog state +layout. `init` does not need `WATCHDOG_L1_RPC_URL`; the RPC URL is read by +each `tick` so it can rotate without editing state: ```bash -export WATCHDOG_ONCE=1 -lua watchdog/main.lua +sequencer-watchdog init ``` -Daemon (staging / production): +After init, schedule `tick`; tick will fail if `head.json` is missing. + +### 6. Run tick + +The watchdog runs **one tick per process, then exits** — there is no daemon +loop. Run it once as a smoke check, then schedule it (systemd timer / k8s +CronJob) and alert on the exit code: ```bash -unset WATCHDOG_ONCE -lua watchdog/main.lua +sequencer-watchdog tick # exit 0 = clean/idle, 1 = transient, 2 = divergence ``` -When `inclusion_block` ≤ last verified checkpoint, the runner only hits `/finalized_state/inclusion_block` and skips L1/CM work. +`sequencer-watchdog` wraps `init` and `tick` with a non-blocking `flock` on +`$WATCHDOG_STATE_DIR/run.lock`, which is released by the kernel if the process +dies. Use the scheduler's non-overlap primitive as well (for example systemd or +Kubernetes CronJob `concurrencyPolicy: Forbid`). A leftover `run.lock` path is +only a lock handle; by itself it does not mean a lock is held. + +When `inclusion_block` ≤ the watchdog checkpoint, the runner only hits `/finalized_state/inclusion_block` and skips L1/CM work. --- @@ -142,16 +182,14 @@ Use Sepolia to validate **the same procedure** you will run on mainnet: internal ### Example env block (fill from ops) ```bash -export WATCHDOG_MODE=compare export WATCHDOG_SEQUENCER_URL="https://" export WATCHDOG_L1_RPC_URL="https://" export WATCHDOG_APP_ADDRESS="0x..." export WATCHDOG_INPUTBOX_ADDRESS="0x..." -export WATCHDOG_CHECKPOINT_DIR="/var/lib/watchdog/checkpoints-sepolia" +export WATCHDOG_STATE_DIR="/var/lib/watchdog/state-sepolia" export WATCHDOG_CM_SNAPSHOT_DIR="/path/to/canonical-machine-image-sepolia" export WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK="" export WATCHDOG_LUA_DEPS="/path/to/sequencer/.deps/lua" -export WATCHDOG_POLL_INTERVAL_SEC=120 ``` ### Operating the Sepolia sequencer @@ -174,19 +212,19 @@ When the rollup runs on Ethereum mainnet, **reuse the same operator checklist ab | L1 RPC | Production-grade archive provider; rate limits matter for wide `getLogs` ranges | | Contracts | Mainnet InputBox, application, portals from production deployment manifest | | CM image | Build from production app/scheduler artifacts (mainnet wallet constants when defined in app-core) | -| `WATCHDOG_POLL_INTERVAL_SEC` | Often 300+; finalized promotion follows mainnet safe head | +| Schedule cadence | A cron/timer interval of 300s+ is fine; finalized promotion follows mainnet safe head | | Security | Stricter firewall between public ingress and internal snapshot tier; secrets management for RPC credentials | -| Bootstrap | Almost always ops-provided CM snapshot or continued checkpoint dir — not genesis replay | +| Bootstrap | Almost always ops-provided CM snapshot or continued state dir — not genesis replay | There is no `just devnet-for-watchdog` or automated harness on mainnet; treat Sepolia compare success as the gate before mainnet go-live. --- -## Compare mode behavior (all live chains) +## Compare Cycle Behavior (All Live Chains) Same on Sepolia and mainnet: -1. Load watchdog checkpoint (or bootstrap CM snapshot). +1. Load watchdog checkpoint from `head.json`. 2. `GET /finalized_state/inclusion_block` — if unchanged, **stop** (cheap). 3. If advanced: `eth_getLogs` on InputBox for `(last_block+1)..inclusion_block`. 4. Advance CM incrementally; `inspect` → SSZ bytes. @@ -198,14 +236,21 @@ Details: [`README.md`](README.md), [`docs/snapshots/lifecycle.md`](../snapshots/ --- -## Checkpoint disk usage +## Checkpoint disk usage and backups Each successful promotion stores a full CM snapshot under -`$WATCHDOG_CHECKPOINT_DIR/checkpoints//`. The watchdog keeps every -checkpoint directory on disk today — there is no automatic pruning. For -long-running deployments, plan operator cleanup: retain the current pointer -target plus one prior checkpoint for rollback forensics, and delete older -`checkpoints/*` directories during maintenance windows. +`$WATCHDOG_STATE_DIR/checkpoints//`, and the watchdog **keeps only +the selected one** — after the atomic `head.json` flip it deletes the +checkpoint it superseded (crash-safe: `head.json` always names a complete +checkpoint). Local disk therefore stays bounded at a single snapshot; no +operator cleanup is required. + +For backups / rollback history, schedule the watchdog tick (it runs one cycle and +exits) and **after it exits** `aws s3 sync $WATCHDOG_STATE_DIR/checkpoints/ +s3://…` (without `--delete`). Because the process has exited there is no race +with its store or prune, and omitting `--delete` **accumulates a per-block +history in S3** while local disk stays at one snapshot. Restore feeds a chosen +snapshot back through the watchdog/sequencer recovery workflow. ## Troubleshooting (live deployments) @@ -213,7 +258,7 @@ target plus one prior checkpoint for rollback forensics, and delete older |---------|----------------| | `/finalized_state` missing on public URL | Wrong tier — use internal `WATCHDOG_SEQUENCER_URL` | | `state_mismatch` | CM image / wallet constants ≠ sequencer build; or wrong bootstrap block | -| `inclusion_block_regressed` | Stale checkpoint dir vs sequencer finalized head | +| `inclusion_block_regressed` | Stale watchdog state vs sequencer finalized head | | Slow or failing `getLogs` | RPC range limits — watchdog uses same partition strategy as sequencer | | Transient `L1 RPC latest head lags target block` | Fallback RPC is behind the sequencer's finalized inclusion block; watchdog retries until the node has indexed through the target (avoids truncated `eth_getLogs` false mismatches) | | `inspect endpoint not implemented` | Rebuild CM image for the correct chain target | diff --git a/docs/watchdog/staging-drills.md b/docs/watchdog/staging-drills.md index 6d5894b..1db2288 100644 --- a/docs/watchdog/staging-drills.md +++ b/docs/watchdog/staging-drills.md @@ -1,6 +1,6 @@ # Watchdog Staging Drills -Operator drills for divergence detection and compare-mode verification. +Operator drills for divergence detection and watchdog tick verification. - **Sepolia / mainnet:** [`operator-deployment.md`](operator-deployment.md). **Local dev:** [`getting-started.md`](getting-started.md). - Module map and local test recipes: [`README.md`](README.md). @@ -9,7 +9,7 @@ This document covers staging and manual verification beyond the devnet tutorial. ## Prerequisites -- **Release bundle (staging/production):** deploy `sequencer-watchdog-vX` and `canonical-machine-image-*-vX` from the same git tag; see [`release/README.md`](../../release/README.md). +- **Release bundle (staging/production):** deploy `sequencer-watchdog-vX` and `canonical-machine-image-*-vX` from the same git tag; toolchain pins live in [`toolchain-pins.env`](../../toolchain-pins.env). - Built canonical machine image: `just canonical-build-machine-image` - `cartesi-machine`, `lua`, and `curl` on PATH - `just watchdog-lua-deps` — builds `lcurl.so` into `.deps/lua` (libcurl + Lua headers on host) @@ -49,39 +49,42 @@ just test-watchdog-compare-harness Or run the Lua compare pass manually after starting a devnet sequencer yourself: ```bash -export WATCHDOG_MODE=compare export WATCHDOG_SEQUENCER_URL=http://127.0.0.1: export WATCHDOG_L1_RPC_URL=http://127.0.0.1:8545 export WATCHDOG_INPUTBOX_ADDRESS= export WATCHDOG_APP_ADDRESS= -export WATCHDOG_CHECKPOINT_DIR=/tmp/watchdog-checkpoints +export WATCHDOG_STATE_DIR=/tmp/watchdog-state export WATCHDOG_CM_SNAPSHOT_DIR=examples/canonical-app/out/canonical-machine-image export WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK=0 +export WATCHDOG_LUA_ROOT="$(pwd)" +export WATCHDOG_LUA_BIN=lua export WATCHDOG_LUA_DEPS=.deps/lua -WATCHDOG_ONCE=1 lua watchdog/main.lua +./watchdog/sequencer-watchdog init +./watchdog/sequencer-watchdog tick ``` -Expected: exit **0**, stderr `watchdog_step` lines ending in `compare pass complete`, and -byte-identical **devnet** genesis SSZ on sequencer `/finalized_state` and CM inspect +Expected: exit **0**; the tick may exit idle if the finalized block is unchanged. +The harness path also proves byte-identical **devnet** genesis SSZ on sequencer `/finalized_state` and CM inspect (same bytes as `wallet_snapshot::encode(WalletConfig::devnet())`; the `.hex` fixture is for Sepolia `default()` — do not use it as the devnet golden). -## Drill 3 — Production compare daemon +## Drill 3 — Production compare (scheduled) -Run the watchdog in compare mode against staging (daemon or cron): +Run the watchdog against staging. Each tick runs one cycle and exits; schedule re-runs +with a systemd timer / cron and alert on the exit code. `sequencer-watchdog` +takes a non-blocking `flock`; production scheduling should also prevent +overlapping ticks with systemd, Kubernetes, or an equivalent scheduler guard: ```bash -export WATCHDOG_MODE=compare -export WATCHDOG_ONCE=1 # or 0 for daemon # ... all WATCHDOG_* vars from config.lua ... -lua watchdog/main.lua +sequencer-watchdog tick ``` -Exit codes from `watchdog/main.lua`: +Exit codes from `sequencer-watchdog tick`: | Code | Meaning | |------|---------| -| `0` | Compare/advance pass completed (or `WATCHDOG_ONCE=1` clean exit) | +| `0` | Compare cycle completed — clean, or idle when finalized is unchanged | | `1` | Transient error after retries (RPC, CM, network) | | `2` | Deterministic divergence — `watchdog_event` on stderr with `{kind, previous_safe_block, sequencer_inclusion_block, mismatch_offset?}` | diff --git a/examples/canonical-app/src/scheduler/mod.rs b/examples/canonical-app/src/scheduler/mod.rs index 385e948..cd12f41 100644 --- a/examples/canonical-app/src/scheduler/mod.rs +++ b/examples/canonical-app/src/scheduler/mod.rs @@ -49,9 +49,18 @@ pub fn run_scheduler_forever( } } Ok(RollupRequest::Inspect { payload }) => { - let report = scheduler - .inspect_state(&payload) - .unwrap_or_else(|err| panic!("scheduler inspect failed: {err:?}")); + // Inspect is a public, read-only query endpoint: an unknown query + // (or a state-encode error) must not halt the guest. Emit a + // structured error report and keep serving, as it did before. + let report = match scheduler.inspect_state(&payload) { + Ok(bytes) => bytes, + Err(core::InspectError::UnsupportedQuery) => { + b"unsupported inspect query".to_vec() + } + Err(core::InspectError::Application(reason)) => { + format!("inspect failed: {reason}").into_bytes() + } + }; rollup .emit_report(&report) .unwrap_or_else(|err| panic!("scheduler failed to emit inspect report: {err}")); @@ -195,6 +204,41 @@ mod tests { ); } + #[test] + fn run_scheduler_reports_unsupported_inspect_query_without_panicking() { + // A non-"state" inspect payload must produce a graceful report, not a + // guest panic. The loop should survive the inspect and only panic when + // it later hits the terminal rollup error. + let inspect = RollupRequest::Inspect { + payload: b"balances".to_vec(), + }; + let terminal_err = Err(RollupError::CmtCallFailed { + operation: "next_input", + code: -22, + }); + let (rollup, reports) = MockRollup::with_inputs(vec![Ok(inspect), terminal_err]); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + run_scheduler_forever( + rollup, + WalletApp::new(WalletConfig::default()), + SchedulerConfig::default(), + ) + })); + + assert!( + result.is_err(), + "scheduler loop should panic only on the terminal rollup error" + ); + let reports = reports.lock().expect("poisoned reports mutex"); + assert!( + reports + .iter() + .any(|report| report.as_slice() == b"unsupported inspect query"), + "expected a graceful unsupported-query report, got: {reports:?}" + ); + } + #[test] fn run_scheduler_emits_report_for_invalid_batch_before_rollup_error() { let sequencer = SchedulerConfig::default().sequencer_address; diff --git a/release/README.md b/release/README.md deleted file mode 100644 index 5b74b53..0000000 --- a/release/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Release bundle versioning - -All release artifacts for tag `vX.Y.Z` share one **bundle version** (`vX.Y.Z`) and one -**toolchain pin set** in [`versions.env`](versions.env). - -| Artifact | Version key | -|----------|-------------| -| `sequencer-vX-linux-{amd64,arm64}.tar.gz` | `vX` (git tag) | -| `canonical-machine-image-{devnet,sepolia}-vX.tar.gz` | `vX` + guest built with pins below | -| `sequencer-watchdog-vX-linux-{amd64,arm64}.tar.gz` | `vX` OCI image (`docker save`) | -| `release-manifest-vX.json` | Lists all artifacts + pins | - -## Single pin source - -Edit **`release/versions.env` only** — CI and release workflows load it via -[`.github/actions/load-release-versions`](../.github/actions/load-release-versions/action.yml). -After editing, run `bash scripts/verify-release-versions.sh` (also enforced in CI) and bump -`rust-toolchain.toml`, `watchdog/third_party/lua-curl/UPSTREAM`, and `LUA_CURL_TARBALL_SHA256` -(when the commit pin changes) together. - -`CARTESI_MACHINE_VERSION` must match: - -- The `cartesi-machine` inside the watchdog image -- The emulator used to build `canonical-machine-image-*` tarballs - -Mismatch causes CM load/advance failures or false `state_mismatch` alarms. - -## Watchdog image - -```bash -docker load < sequencer-watchdog-vX-linux-amd64.tar.gz -docker run --rm \ - -e WATCHDOG_MODE=compare \ - -e WATCHDOG_ONCE=1 \ - -e WATCHDOG_SEQUENCER_URL=... \ - -e WATCHDOG_L1_RPC_URL=... \ - -e WATCHDOG_INPUTBOX_ADDRESS=... \ - -e WATCHDOG_APP_ADDRESS=... \ - -e WATCHDOG_CHECKPOINT_DIR=/checkpoints \ - -e WATCHDOG_CM_SNAPSHOT_DIR=/cm-bootstrap \ - -v /var/lib/watchdog/checkpoints:/checkpoints \ - -v /var/lib/watchdog/cm-bootstrap:/cm-bootstrap:ro \ - sequencer-watchdog:vX -``` - -Mount `canonical-machine-image-sepolia-vX.tar.gz` extract for bootstrap -(`WATCHDOG_CM_SNAPSHOT_DIR`) on first run. - -Inspect alignment: - -```bash -docker inspect --format '{{ index .Config.Labels "org.cartesi.sequencer.release-tag" }}' IMAGE -cat /opt/watchdog/RELEASE.json # inside container -``` diff --git a/scripts/ci-watchdog-docker-smoke.sh b/scripts/ci-watchdog-docker-smoke.sh index 857857e..49ddacf 100755 --- a/scripts/ci-watchdog-docker-smoke.sh +++ b/scripts/ci-watchdog-docker-smoke.sh @@ -6,7 +6,7 @@ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "${root}" # shellcheck disable=SC1091 -source release/versions.env +source toolchain-pins.env image="sequencer-watchdog:ci-smoke" if command -v dpkg >/dev/null 2>&1; then @@ -35,13 +35,73 @@ docker build \ --build-arg "GIT_COMMIT=local" \ --build-arg "CARTESI_MACHINE_VERSION=${CARTESI_MACHINE_VERSION}" \ --build-arg "CARTESI_MACHINE_DEB_SHA256=${deb_sha}" \ - --build-arg "LUA_CURL_UPSTREAM_SHA=${LUA_CURL_UPSTREAM_SHA}" \ -f watchdog/Dockerfile \ -t "${image}" \ . docker run --rm -e WATCHDOG_PRINT_RELEASE_INFO=1 "${image}" >/dev/null docker run --rm --entrypoint cartesi-machine "${image}" --version >/dev/null +docker run --rm --entrypoint flock "${image}" --version >/dev/null docker run --rm --entrypoint lua5.4 "${image}" -e "require('cartesi'); print('cartesi ok')" +# Validate the vendored lcurl build loads in the runtime image. +docker run --rm --entrypoint lua5.4 "${image}" -e "require('lcurl'); print('lcurl ok')" + +state_dir="$(mktemp -d)" +lock_container="" +cleanup() { + if [[ -n "${lock_container}" ]]; then + docker rm -f "${lock_container}" >/dev/null 2>&1 || true + fi + rm -rf "${state_dir}" +} +trap cleanup EXIT + +set +e +tick_output="$( + docker run --rm \ + -e WATCHDOG_STATE_DIR=/state \ + -v "${state_dir}:/state" \ + "${image}" tick 2>&1 +)" +tick_status=$? +set -e +if [[ "${tick_status}" -ne 1 || "${tick_output}" != *"failed to load config.json"* ]]; then + echo "expected unlocked tick to reach Lua and fail on missing config.json" >&2 + echo "status=${tick_status}" >&2 + echo "${tick_output}" >&2 + exit 1 +fi + +lock_container="sequencer-watchdog-lock-smoke-$$" +docker run -d \ + --name "${lock_container}" \ + -v "${state_dir}:/state" \ + --entrypoint sh \ + "${image}" \ + -c 'flock /state/run.lock sleep 30' >/dev/null + +locked="" +for _ in $(seq 1 30); do + set +e + lock_output="$( + docker run --rm \ + -e WATCHDOG_STATE_DIR=/state \ + -v "${state_dir}:/state" \ + "${image}" tick 2>&1 + )" + lock_status=$? + set -e + if [[ "${lock_status}" -eq 1 && "${lock_output}" == *"watchdog state is already locked"* ]]; then + locked=1 + break + fi + sleep 0.2 +done +if [[ -z "${locked}" ]]; then + echo "expected tick to fail on held watchdog lock" >&2 + echo "last status=${lock_status}" >&2 + echo "${lock_output}" >&2 + exit 1 +fi echo "watchdog docker smoke ok" diff --git a/scripts/generate-release-manifest.sh b/scripts/generate-release-manifest.sh index 45d289d..5bfb75f 100755 --- a/scripts/generate-release-manifest.sh +++ b/scripts/generate-release-manifest.sh @@ -3,7 +3,7 @@ set -euo pipefail root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -versions="${root}/release/versions.env" +pins="${root}/toolchain-pins.env" usage() { echo "usage: $0 --tag TAG [--git-sha SHA] [--output PATH]" >&2 @@ -42,14 +42,14 @@ if [[ -z "${git_sha}" ]]; then git_sha="$(git -C "${root}" rev-parse HEAD 2>/dev/null || echo unknown)" fi -if [[ ! -f "${versions}" ]]; then - echo "missing ${versions}" >&2 +if [[ ! -f "${pins}" ]]; then + echo "missing ${pins}" >&2 exit 1 fi # shellcheck disable=SC1090 set -a -source "${versions}" +source "${pins}" set +a artifacts_json="$(cat < "${output:-/dev/stdout}" +emit_manifest() { + python3 - "${tag}" "${git_sha}" "${artifacts_json}" <<'PY' import json, os, sys tag, git_sha, artifacts_json = sys.argv[1], sys.argv[2], sys.argv[3] @@ -78,7 +79,6 @@ manifest = { "rust": os.environ["RUST_TOOLCHAIN"], "xgenext2fs": os.environ["XGENEXT2FS_VERSION"], "cartesi_machine": os.environ["CARTESI_MACHINE_VERSION"], - "lua_curl_upstream_sha": os.environ["LUA_CURL_UPSTREAM_SHA"], }, "artifacts": artifacts, "alignment": { @@ -89,3 +89,10 @@ manifest = { json.dump(manifest, sys.stdout, indent=2) sys.stdout.write("\n") PY +} + +if [[ -n "${output}" ]]; then + emit_manifest > "${output}" +else + emit_manifest +fi diff --git a/scripts/load-toolchain-pins.sh b/scripts/load-toolchain-pins.sh new file mode 100755 index 0000000..340a4e3 --- /dev/null +++ b/scripts/load-toolchain-pins.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Parse toolchain-pins.env and append KEY=value lines to a GitHub env file. +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +pins="${TOOLCHAIN_PINS_FILE:-${root}/toolchain-pins.env}" +output="${1:-${GITHUB_ENV:-}}" + +usage() { + echo "usage: $0 OUTPUT_FILE" >&2 + echo " $0 - # print parsed KEY=value lines to stdout" >&2 + exit 1 +} + +trim() { + local value="$1" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + printf '%s' "${value}" +} + +emit() { + if [[ "${output}" == "-" ]]; then + printf '%s\n' "$1" + else + printf '%s\n' "$1" >> "${output}" + fi +} + +if [[ -z "${output}" ]]; then + usage +fi +if [[ ! -f "${pins}" ]]; then + echo "missing ${pins}" >&2 + exit 1 +fi + +while IFS= read -r line || [[ -n "${line}" ]]; do + line="${line%%#*}" + line="$(trim "${line}")" + if [[ -z "${line}" ]]; then + continue + fi + if [[ ! "${line}" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then + echo "invalid line in ${pins}: ${line}" >&2 + exit 1 + fi + emit "${line}" +done < "${pins}" diff --git a/scripts/verify-release-versions.sh b/scripts/verify-release-versions.sh deleted file mode 100755 index beac007..0000000 --- a/scripts/verify-release-versions.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -# Fail if release/versions.env drifts from other pinned artifacts in-tree. -set -euo pipefail - -root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -versions="${root}/release/versions.env" - -if [[ ! -f "${versions}" ]]; then - echo "missing ${versions}" >&2 - exit 1 -fi - -# shellcheck disable=SC1090 -set -a -source "${versions}" -set +a - -errors=0 - -rust_channel="$( - grep -E '^\s*channel\s*=' "${root}/rust-toolchain.toml" \ - | head -1 \ - | sed -E 's/.*=\s*"([^"]+)".*/\1/' -)" -if [[ "${rust_channel}" != "${RUST_TOOLCHAIN}" ]]; then - echo "rust-toolchain.toml channel=${rust_channel} != RUST_TOOLCHAIN=${RUST_TOOLCHAIN}" >&2 - errors=$((errors + 1)) -fi - -upstream_file="${root}/watchdog/third_party/lua-curl/UPSTREAM" -if [[ -f "${upstream_file}" ]]; then - upstream_sha="$(grep -E '^Commit:' "${upstream_file}" | awk '{print $2}')" - if [[ "${upstream_sha}" != "${LUA_CURL_UPSTREAM_SHA}" ]]; then - echo "lua-curl UPSTREAM commit=${upstream_sha} != LUA_CURL_UPSTREAM_SHA=${LUA_CURL_UPSTREAM_SHA}" >&2 - errors=$((errors + 1)) - fi - if [[ -z "${LUA_CURL_TARBALL_SHA256:-}" ]]; then - echo "LUA_CURL_TARBALL_SHA256 missing in ${versions}" >&2 - errors=$((errors + 1)) - fi -else - echo "missing ${upstream_file}" >&2 - errors=$((errors + 1)) -fi - -if [[ "${errors}" -ne 0 ]]; then - exit 1 -fi - -echo "release version pins aligned" diff --git a/scripts/verify-toolchain-pins.sh b/scripts/verify-toolchain-pins.sh new file mode 100755 index 0000000..8e17668 --- /dev/null +++ b/scripts/verify-toolchain-pins.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Fail if toolchain-pins.env drifts from other pinned artifacts in-tree. +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +pins="${root}/toolchain-pins.env" + +if [[ ! -f "${pins}" ]]; then + echo "missing ${pins}" >&2 + exit 1 +fi + +# shellcheck disable=SC1090 +set -a +source "${pins}" +set +a + +errors=0 + +rust_channel="$( + grep -E '^\s*channel\s*=' "${root}/rust-toolchain.toml" \ + | head -1 \ + | sed -E 's/.*=[[:space:]]*"([^"]+)".*/\1/' +)" +if [[ "${rust_channel}" != "${RUST_TOOLCHAIN}" ]]; then + echo "rust-toolchain.toml channel=${rust_channel} != RUST_TOOLCHAIN=${RUST_TOOLCHAIN}" >&2 + errors=$((errors + 1)) +fi + +if [[ "${errors}" -ne 0 ]]; then + exit 1 +fi + +echo "toolchain pins aligned" diff --git a/scripts/watchdog-lua-deps.sh b/scripts/watchdog-lua-deps.sh index 50358f7..d9c283c 100755 --- a/scripts/watchdog-lua-deps.sh +++ b/scripts/watchdog-lua-deps.sh @@ -1,75 +1,17 @@ #!/usr/bin/env bash -# Build watchdog Lua native deps: lcurl (lua-cURLv3) into .deps/lua. -# Sources are fetched at build time (pinned); only UPSTREAM is tracked in git. -# JSON is pure Lua under watchdog/third_party/json.lua (no compile step). +# Build the watchdog's native Lua dep: lcurl (lua-cURLv3) -> .deps/lua/lcurl.so. +# +# Sources are vendored in-tree under watchdog/third_party/lua-curl/src (a curated +# C subset; see watchdog/third_party/lua-curl/UPSTREAM for provenance). There is +# no build-time download and no pin to verify -- the compiled bytes are exactly +# the in-tree source. libcurl must be installed on the host. JSON is pure Lua +# under watchdog/third_party/json.lua (no compile step). set -euo pipefail root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +src_dir="${root}/watchdog/third_party/lua-curl/src" out_dir="${root}/.deps/lua" out_so="${out_dir}/lcurl.so" -upstream_file="${root}/watchdog/third_party/lua-curl/UPSTREAM" -versions_file="${root}/release/versions.env" - -resolve_upstream_sha() { - if [[ -n "${LUA_CURL_UPSTREAM_SHA:-}" ]]; then - echo "${LUA_CURL_UPSTREAM_SHA}" - return - fi - if [[ -f "${versions_file}" ]]; then - # shellcheck disable=SC1090 - local from_versions - from_versions="$( - set -a - source "${versions_file}" - set +a - echo "${LUA_CURL_UPSTREAM_SHA:-}" - )" - if [[ -n "${from_versions}" ]]; then - echo "${from_versions}" - return - fi - fi - if [[ -f "${upstream_file}" ]]; then - grep -E '^Commit:' "${upstream_file}" | awk '{print $2}' - return - fi -} - -resolve_tarball_sha256() { - if [[ -n "${LUA_CURL_TARBALL_SHA256:-}" ]]; then - echo "${LUA_CURL_TARBALL_SHA256}" - return - fi - if [[ -f "${versions_file}" ]]; then - # shellcheck disable=SC1090 - local from_versions - from_versions="$( - set -a - source "${versions_file}" - set +a - echo "${LUA_CURL_TARBALL_SHA256:-}" - )" - if [[ -n "${from_versions}" ]]; then - echo "${from_versions}" - return - fi - fi -} - -upstream_sha="$(resolve_upstream_sha)" -if [[ -z "${upstream_sha}" ]]; then - echo "watchdog-lua-deps: could not resolve Lua-cURLv3 upstream pin" >&2 - exit 1 -fi - -tarball_sha256="$(resolve_tarball_sha256)" -if [[ -z "${tarball_sha256}" ]]; then - echo "watchdog-lua-deps: could not resolve Lua-cURLv3 tarball sha256 pin" >&2 - exit 1 -fi - -upstream_tar="https://github.com/Lua-cURL/Lua-cURLv3/archive/${upstream_sha}.tar.gz" -src_cache="${root}/.deps/lua-curl-src/${upstream_sha}" mkdir -p "${out_dir}" @@ -85,7 +27,6 @@ resolve_lua_bin() { fi done } - lua_bin="$(resolve_lua_bin || true)" lcurl_loadable() { @@ -93,102 +34,71 @@ lcurl_loadable() { "${lua_bin}" -e "package.cpath='${out_dir}/?.so;'..package.cpath; require('lcurl')" >/dev/null 2>&1 } +# Rebuild only if the .so is missing/unloadable or older than any vendored source. +needs_build=1 if [[ -f "${out_so}" ]] && lcurl_loadable; then - exit 0 + needs_build=0 + while IFS= read -r src; do + if [[ "${src}" -nt "${out_so}" ]]; then + needs_build=1 + break + fi + done < <(find "${src_dir}" -type f \( -name '*.c' -o -name '*.h' \)) fi - -fetch_sources() { - echo "watchdog-lua-deps: fetching Lua-cURLv3 ${upstream_sha}" >&2 - local tmp archive - tmp="$(mktemp -d)" - archive="${tmp}/lua-curl.tar.gz" - trap 'rm -rf "${tmp}"' RETURN - if command -v curl >/dev/null 2>&1; then - curl -fsSL "${upstream_tar}" -o "${archive}" - elif command -v wget >/dev/null 2>&1; then - wget -qO "${archive}" "${upstream_tar}" - else - echo "watchdog-lua-deps: need curl or wget to fetch Lua-cURLv3" >&2 - exit 1 - fi - echo "${tarball_sha256} ${archive}" | sha256sum --check - tar -xzf "${archive}" -C "${tmp}" - shopt -s nullglob - local dirs=("${tmp}"/Lua-cURLv3-*) - if [[ ${#dirs[@]} -ne 1 ]]; then - echo "watchdog-lua-deps: unexpected Lua-cURLv3 extract layout" >&2 - exit 1 - fi - rm -rf "${src_cache}" - mkdir -p "$(dirname "${src_cache}")" - cp -a "${dirs[0]}" "${src_cache}" -} - -if [[ ! -f "${src_cache}/Makefile" ]]; then - fetch_sources +if [[ "${needs_build}" -eq 0 ]]; then + exit 0 fi -lua_inc="${LUA_INC:-}" -if [[ -z "${lua_inc}" ]]; then -for dir in /usr/include/lua5.4 /usr/include/lua5.3 /usr/include/lua; do - if [[ -f "${dir}/lua.h" ]]; then - lua_inc="${dir}" - break - fi -done +# Lua headers: prefer pkg-config (covers nix / Homebrew / Debian), then the +# usual Debian include dirs, then an explicit LUA_INC override. +lua_cflags="" +if [[ -n "${LUA_INC:-}" ]]; then + lua_cflags="-I${LUA_INC}" +else + for impl in lua5.4 lua5.3 lua; do + if pkg-config --exists "${impl}" 2>/dev/null; then + lua_cflags="$(pkg-config --cflags "${impl}")" + break + fi + done fi - -if [[ -z "${lua_inc}" ]]; then - echo "watchdog-lua-deps: install Lua headers (e.g. lua5.4-dev)" >&2 - exit 1 +if [[ -z "${lua_cflags}" ]]; then + for dir in /usr/include/lua5.4 /usr/include/lua5.3 /usr/include/lua; do + if [[ -f "${dir}/lua.h" ]]; then + lua_cflags="-I${dir}" + break + fi + done fi - -if ! command -v make >/dev/null 2>&1; then - echo "watchdog-lua-deps: install make" >&2 +if [[ -z "${lua_cflags}" ]]; then + echo "watchdog-lua-deps: Lua headers not found; install lua5.4-dev or set LUA_INC" >&2 exit 1 fi if ! pkg-config --exists libcurl 2>/dev/null; then - echo "watchdog-lua-deps: install libcurl dev package (libcurl4-openssl-dev or similar)" >&2 + echo "watchdog-lua-deps: libcurl dev package not found (libcurl4-openssl-dev or similar)" >&2 exit 1 fi -# Lua-cURL Makefile uses LUA_INC (not LUA_INCLUDE_DIR). On Debian/Ubuntu headers -# live under /usr/include/lua5.4/, not /usr/include/. -lua_impl="" -if pkg-config --exists lua5.4 2>/dev/null; then - lua_impl="lua5.4" -elif pkg-config --exists lua5.3 2>/dev/null; then - lua_impl="lua5.3" -fi - -make_args=( - "LUA_INC=${lua_inc}" - "CURL_LIBS=$(pkg-config --libs libcurl)" -) -if [[ -z "${lua_impl}" && "${lua_inc}" == *lua5.4* ]]; then - lua_impl="lua5.4" -elif [[ -z "${lua_impl}" && "${lua_inc}" == *lua5.3* ]]; then - lua_impl="lua5.3" -fi -if [[ -n "${lua_impl}" ]]; then - make_args+=("LUA_IMPL=${lua_impl}") -fi - -make -C "${src_cache}" "${make_args[@]}" >/dev/null - -built_so="$(find "${src_cache}" -name 'lcurl.so' -o -name 'cURL.so' | head -1)" -if [[ -z "${built_so}" ]]; then - echo "watchdog-lua-deps: make succeeded but lcurl.so not found" >&2 - exit 1 -fi -cp "${built_so}" "${out_so}" +# Match the upstream Makefile's essential flags; on macOS a Lua C module is a +# bundle with dynamic_lookup, on Linux a plain shared object. +case "$(uname)" in + Darwin) os_flags=(-bundle -undefined dynamic_lookup) ;; + *) os_flags=(-shared) ;; +esac + +echo "watchdog-lua-deps: compiling vendored lcurl.so" >&2 +# shellcheck disable=SC2046 # intentional word-splitting of pkg-config output +"${CC:-cc}" -O2 -pipe -fPIC "${os_flags[@]}" -Wall -Wno-unused-value -DPTHREADS \ + ${lua_cflags} $(pkg-config --cflags libcurl) \ + "${src_dir}"/*.c \ + -o "${out_so}" \ + $(pkg-config --libs libcurl) if [[ -z "${lua_bin}" ]]; then echo "watchdog-lua-deps: install lua5.4 (or set LUA_BIN) to verify lcurl.so" >&2 exit 1 fi - if ! lcurl_loadable; then echo "watchdog-lua-deps: built lcurl.so but ${lua_bin} cannot load it (Lua version mismatch?)" >&2 exit 1 diff --git a/tests/e2e/src/bin/devnet_stack.rs b/tests/e2e/src/bin/devnet_stack.rs index 9369a97..e49ca4b 100644 --- a/tests/e2e/src/bin/devnet_stack.rs +++ b/tests/e2e/src/bin/devnet_stack.rs @@ -22,7 +22,7 @@ async fn main() -> HarnessResult<()> { ManagedSequencer::spawn(devnet_sequencer_config_no_faketime("devnet-stack")).await?; let machine_image = paths::devnet_machine_image_path(); - let checkpoint_dir = std::env::temp_dir().join("watchdog-checkpoints-devnet"); + let state_dir = std::env::temp_dir().join("watchdog-state-devnet"); eprintln!(); eprintln!("=== Devnet stack is up ==="); @@ -31,8 +31,9 @@ async fn main() -> HarnessResult<()> { eprintln!("App address: {}", runtime.app_address()); eprintln!("InputBox: {}", runtime.input_box_address()); eprintln!(); - eprintln!("--- export these (watchdog compare mode) ---"); - eprintln!("export WATCHDOG_MODE=compare"); + eprintln!( + "--- export these, then run: ./watchdog/sequencer-watchdog init && ./watchdog/sequencer-watchdog tick ---" + ); eprintln!("export WATCHDOG_SEQUENCER_URL={}", runtime.endpoint()); eprintln!("export WATCHDOG_L1_RPC_URL={}", runtime.l1_endpoint()); eprintln!( @@ -40,10 +41,7 @@ async fn main() -> HarnessResult<()> { runtime.input_box_address() ); eprintln!("export WATCHDOG_APP_ADDRESS={}", runtime.app_address()); - eprintln!( - "export WATCHDOG_CHECKPOINT_DIR={}", - checkpoint_dir.display() - ); + eprintln!("export WATCHDOG_STATE_DIR={}", state_dir.display()); eprintln!( "export WATCHDOG_CM_SNAPSHOT_DIR={}", machine_image.display() @@ -53,7 +51,6 @@ async fn main() -> HarnessResult<()> { "export WATCHDOG_LUA_DEPS={}/.deps/lua", paths::workspace_root().display() ); - eprintln!("export WATCHDOG_ONCE=1 # or omit for daemon loop"); eprintln!(); eprintln!("Wait for finalized snapshot (404 until promotion):"); eprintln!( @@ -62,7 +59,10 @@ async fn main() -> HarnessResult<()> { ); eprintln!(); eprintln!("Run watchdog (from repo root, after `just watchdog-lua-deps`):"); - eprintln!(" lua watchdog/main.lua"); + eprintln!(" export WATCHDOG_LUA_ROOT=$(pwd)"); + eprintln!(" export WATCHDOG_LUA_BIN=lua"); + eprintln!(" ./watchdog/sequencer-watchdog init"); + eprintln!(" ./watchdog/sequencer-watchdog tick"); eprintln!(); eprintln!("Press Ctrl+C here to stop Anvil + sequencer."); diff --git a/tests/e2e/src/test_cases.rs b/tests/e2e/src/test_cases.rs index 827033e..08e4df9 100644 --- a/tests/e2e/src/test_cases.rs +++ b/tests/e2e/src/test_cases.rs @@ -956,7 +956,9 @@ async fn run_recovery_after_stale_batches_test( let mut alice_l2 = runtime.wallet_l2(alice.clone())?; let mut replay_before = ReplayWalletApp::devnet(); - let deposit_amount = U256::from(600_000_u64); + // Large deposit leaves headroom for the post-recovery batch-close pump + // that drives a finalized snapshot for watchdog compare. + let deposit_amount = U256::from(10_000_000_u64); let transfer_amount = U256::from(100_000_u64); let post_recovery_transfer = U256::from(200_000_u64); let gas = fee_to_linear(DEFAULT_FRAME_FEE); @@ -1045,6 +1047,16 @@ async fn run_recovery_after_stale_batches_test( ); assert_eq!(replay_after.current_user_nonce(alice_address), 1); + drive_finalized_gold_batch_for_watchdog( + runtime, + &mut ws_after, + &mut replay_after, + &mut alice_l2_fresh, + alice_address, + ) + .await?; + crate::watchdog_compare::run_watchdog_non_genesis_compare_test(runtime).await?; + Ok(()) } diff --git a/tests/e2e/src/watchdog_compare.rs b/tests/e2e/src/watchdog_compare.rs index b702479..fc17589 100644 --- a/tests/e2e/src/watchdog_compare.rs +++ b/tests/e2e/src/watchdog_compare.rs @@ -83,32 +83,47 @@ pub async fn run_watchdog_genesis_compare_test( .into()); } - eprintln!("[watchdog-harness] step 3/6: prepare watchdog checkpoint dir"); - let checkpoint_dir = tempfile::tempdir() - .map_err(|err| format!("temp checkpoint dir: {err}"))? + eprintln!("[watchdog-harness] step 3/6: prepare watchdog state dir"); + let state_dir = tempfile::tempdir() + .map_err(|err| format!("temp watchdog state dir: {err}"))? .keep(); - eprintln!("[watchdog-harness] step 4/6: run production main.lua pass (machine_cartesi)"); - run_lua_main_compare( + eprintln!("[watchdog-harness] step 4/6: initialize watchdog state (machine_cartesi)"); + run_lua_main_success( runtime, workspace.as_path(), - checkpoint_dir.as_path(), + state_dir.as_path(), machine_image.as_path(), + "init", ) .await?; eprintln!( - "[watchdog-harness] step 5/6: run a second main.lua compare pass (checkpoint reload)" + "[watchdog-harness] step 5/6: run production watchdog tick (genesis unchanged -> idle skip)" ); - run_lua_main_compare( + // The Rust checks above prove sequencer/CM byte parity at genesis. The + // watchdog tick itself intentionally idles because init selected block 0 + // and the sequencer finalized block is still 0; init is not a compare. + run_lua_main_success( runtime, workspace.as_path(), - checkpoint_dir.as_path(), + state_dir.as_path(), machine_image.as_path(), + "tick", ) .await?; - eprintln!("[watchdog-harness] step 6/6: compare pass completed successfully"); + eprintln!("[watchdog-harness] step 6/6: run a second tick (idempotent idle skip)"); + run_lua_main_success( + runtime, + workspace.as_path(), + state_dir.as_path(), + machine_image.as_path(), + "tick", + ) + .await?; + + eprintln!("[watchdog-harness] compare pass completed successfully"); Ok(()) } @@ -154,28 +169,40 @@ pub async fn run_watchdog_non_genesis_compare_test( return Err("expected non-genesis finalized_state executed_input_count > 0".into()); } - eprintln!("[watchdog-harness] step 2/4: prepare watchdog checkpoint dir"); - let checkpoint_dir = tempfile::tempdir() - .map_err(|err| format!("temp checkpoint dir: {err}"))? + eprintln!("[watchdog-harness] step 2/5: prepare watchdog state dir"); + let state_dir = tempfile::tempdir() + .map_err(|err| format!("temp watchdog state dir: {err}"))? .keep(); - eprintln!("[watchdog-harness] step 3/4: run production main.lua pass (machine_cartesi)"); - run_lua_main_compare( + eprintln!("[watchdog-harness] step 3/5: initialize watchdog state (machine_cartesi)"); + run_lua_main_success( + runtime, + workspace.as_path(), + state_dir.as_path(), + machine_image.as_path(), + "init", + ) + .await?; + + eprintln!("[watchdog-harness] step 4/5: run production watchdog tick"); + run_lua_main_success( runtime, workspace.as_path(), - checkpoint_dir.as_path(), + state_dir.as_path(), machine_image.as_path(), + "tick", ) .await?; eprintln!( - "[watchdog-harness] step 4/4: run a second main.lua compare pass (checkpoint reload)" + "[watchdog-harness] step 5/5: run a second tick (idempotent re-run: unchanged finalized -> skip)" ); - run_lua_main_compare( + run_lua_main_success( runtime, workspace.as_path(), - checkpoint_dir.as_path(), + state_dir.as_path(), machine_image.as_path(), + "tick", ) .await?; Ok(()) @@ -208,17 +235,24 @@ pub async fn run_watchdog_non_genesis_divergence_test( .ok_or("finalized_state response missing X-Inclusion-Block header")?; eprintln!("[watchdog-harness] divergence target inclusion_block={inclusion_block}"); - eprintln!( - "[watchdog-harness] divergence step 2/3: run main.lua against mismatched sepolia snapshot" - ); - let checkpoint_dir = tempfile::tempdir() - .map_err(|err| format!("temp checkpoint dir: {err}"))? + eprintln!("[watchdog-harness] divergence step 2/3: initialize mismatched state and run tick"); + let state_dir = tempfile::tempdir() + .map_err(|err| format!("temp watchdog state dir: {err}"))? .keep(); + run_lua_main_success( + runtime, + workspace.as_path(), + state_dir.as_path(), + mismatch_image.as_path(), + "init", + ) + .await?; let output = run_lua_main( runtime, workspace.as_path(), - checkpoint_dir.as_path(), + state_dir.as_path(), mismatch_image.as_path(), + "tick", ) .await?; let exit = output.status.code().unwrap_or(-1); @@ -240,8 +274,7 @@ pub async fn run_watchdog_non_genesis_divergence_test( fn compare_env( runtime: &ManagedSequencer, - workspace: &Path, - checkpoint_dir: &Path, + state_dir: &Path, machine_image: &Path, lua_deps: &Path, ) -> Vec<(String, String)> { @@ -250,8 +283,6 @@ fn compare_env( "WATCHDOG_LUA_DEPS".into(), lua_deps.to_string_lossy().into_owned(), ), - ("WATCHDOG_MODE".into(), "compare".into()), - ("WATCHDOG_ONCE".into(), "1".into()), ( "WATCHDOG_SEQUENCER_URL".into(), runtime.endpoint().to_string(), @@ -269,8 +300,8 @@ fn compare_env( runtime.app_address().to_string(), ), ( - "WATCHDOG_CHECKPOINT_DIR".into(), - checkpoint_dir.to_string_lossy().into_owned(), + "WATCHDOG_STATE_DIR".into(), + state_dir.to_string_lossy().into_owned(), ), ( "WATCHDOG_CM_SNAPSHOT_DIR".into(), @@ -280,58 +311,46 @@ fn compare_env( "WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK".into(), GENESIS_SAFE_BLOCK.into(), ), - ("WATCHDOG_CM_EXECUTABLE".into(), "cartesi-machine".into()), - ( - "WATCHDOG_CM_WORK_DIR".into(), - workspace - .join(".watchdog-cm-work") - .to_string_lossy() - .into_owned(), - ), ] } async fn run_lua_main( runtime: &mut ManagedSequencer, workspace: &Path, - checkpoint_dir: &Path, + state_dir: &Path, machine_image: &Path, + command_name: &str, ) -> ScenarioResult { let lua_deps = workspace.join(".deps/lua"); ensure_lcurl(lua_deps.as_path())?; - std::fs::create_dir_all(workspace.join(".watchdog-cm-work")) - .map_err(|err| format!("create watchdog CM work dir: {err}"))?; - let mut command = Command::new("lua"); + let mut command = Command::new(workspace.join("watchdog/sequencer-watchdog")); command .current_dir(workspace) - .arg("watchdog/main.lua") + .arg(command_name) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - for (key, value) in compare_env( - runtime, - workspace, - checkpoint_dir, - machine_image, - lua_deps.as_path(), - ) { + command.env("WATCHDOG_LUA_ROOT", workspace); + command.env("WATCHDOG_LUA_BIN", "lua"); + for (key, value) in compare_env(runtime, state_dir, machine_image, lua_deps.as_path()) { command.env(key, value); } command .output() .await - .map_err(|err| format!("failed to run watchdog main.lua: {err}").into()) + .map_err(|err| format!("failed to run sequencer-watchdog: {err}").into()) } -async fn run_lua_main_compare( +async fn run_lua_main_success( runtime: &mut ManagedSequencer, workspace: &Path, - checkpoint_dir: &Path, + state_dir: &Path, machine_image: &Path, + command_name: &str, ) -> ScenarioResult<()> { - let output = run_lua_main(runtime, workspace, checkpoint_dir, machine_image).await?; + let output = run_lua_main(runtime, workspace, state_dir, machine_image, command_name).await?; if !output.status.success() { return Err(format!( - "watchdog main.lua exited with {}; stderr: {}", + "sequencer-watchdog {command_name} exited with {}; stderr: {}", output.status, String::from_utf8_lossy(output.stderr.as_slice()) ) @@ -385,9 +404,7 @@ async fn prove_cm_inspect_genesis( let report = std::fs::read(report_path.as_path()) .map_err(|err| format!("read inspect report: {err}"))?; - if report.starts_with(b"inspect endpoint not implemented".as_slice()) - || report.starts_with("inspect endpoint not implemented".as_bytes()) - { + if report.starts_with(b"inspect endpoint not implemented".as_slice()) { return Err( "CM dapp is stale (inspect not implemented); rebuild with: just canonical-build-machine-image" .into(), diff --git a/release/versions.env b/toolchain-pins.env similarity index 67% rename from release/versions.env rename to toolchain-pins.env index f16e7c4..98e745e 100644 --- a/release/versions.env +++ b/toolchain-pins.env @@ -1,5 +1,5 @@ -# Single source of truth for release + CI toolchain pins. -# Workflows load this via .github/actions/load-release-versions. +# Single source of truth for CI + release toolchain pins. +# Workflows load this via .github/actions/load-toolchain-pins. # Release tag (git v*) is the sequencer / watchdog / CM image bundle version. RUST_TOOLCHAIN=1.95.0 @@ -14,6 +14,5 @@ CARTESI_MACHINE_VERSION=v0.20.0-test2 CARTESI_MACHINE_SHA256_AMD64=39bbfc96a6cc6606307294b719df65f4f2725e8d200d062bcbd8c22355b99b56 CARTESI_MACHINE_SHA256_ARM64=787d823756000cdecd72da8a3494b4c08613087379035959e561bbaef7a220ba -# Lua-cURLv3 pin (watchdog-lua-deps + watchdog image build). -LUA_CURL_UPSTREAM_SHA=9f8b6dba8b5ef1b26309a571ae75cda4034279e5 -LUA_CURL_TARBALL_SHA256=784d585c03db13074d23ffc8fb698995fe5d9326f6f169ee009c559f2ad52b88 +# lua-cURLv3 is vendored in-tree under watchdog/third_party/lua-curl/src (no pin +# to track -- the compiled bytes are the in-tree source). See its UPSTREAM file. diff --git a/watchdog/Dockerfile b/watchdog/Dockerfile index f53e970..85b847e 100644 --- a/watchdog/Dockerfile +++ b/watchdog/Dockerfile @@ -1,5 +1,5 @@ -# Watchdog compare-mode runtime image. -# Build args RELEASE_TAG, GIT_COMMIT, and cartesi-machine pins must match release/versions.env +# Watchdog one-cycle runtime image. +# Build args RELEASE_TAG, GIT_COMMIT, and cartesi-machine pins must match toolchain-pins.env # and the canonical-machine-image-* tarballs produced in the same release. # cartesi-machine v0.20.x libs require glibc >= 2.38 (bookworm is 2.36). @@ -11,13 +11,13 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ARG CARTESI_MACHINE_VERSION ARG CARTESI_MACHINE_DEB_SHA256 ARG TARGETARCH -ARG LUA_CURL_UPSTREAM_SHA +# wget + ca-certificates fetch the cartesi-machine .deb; gcc/pkg-config + Lua +# headers compile the vendored lcurl.so. lua-curl is vendored in-tree under +# watchdog/third_party/lua-curl/src, so the build needs no network for it. RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ - curl \ wget \ - make \ gcc \ pkg-config \ lua5.4 \ @@ -28,11 +28,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ WORKDIR /build COPY scripts/watchdog-lua-deps.sh scripts/watchdog-lua-deps.sh -COPY release/versions.env release/versions.env -COPY watchdog/third_party/lua-curl/UPSTREAM watchdog/third_party/lua-curl/UPSTREAM -COPY watchdog/third_party/json.lua watchdog/third_party/json.lua +COPY watchdog/third_party watchdog/third_party -ENV LUA_CURL_UPSTREAM_SHA=${LUA_CURL_UPSTREAM_SHA} RUN bash scripts/watchdog-lua-deps.sh RUN case "${TARGETARCH}" in \ @@ -57,14 +54,12 @@ FROM debian:${DEBIAN_VERSION} ARG RELEASE_TAG ARG GIT_COMMIT ARG CARTESI_MACHINE_VERSION -ARG LUA_CURL_UPSTREAM_SHA LABEL org.opencontainers.image.title="sequencer-watchdog" \ org.opencontainers.image.version="${RELEASE_TAG}" \ org.opencontainers.image.revision="${GIT_COMMIT}" \ org.cartesi.sequencer.release-tag="${RELEASE_TAG}" \ - org.cartesi.cartesi-machine.version="${CARTESI_MACHINE_VERSION}" \ - org.cartesi.lua-curl.upstream-sha="${LUA_CURL_UPSTREAM_SHA}" + org.cartesi.cartesi-machine.version="${CARTESI_MACHINE_VERSION}" RUN apt-get update && apt-get install -y --no-install-recommends \ lua5.4 \ @@ -72,6 +67,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libslirp0 \ libgomp1 \ ca-certificates \ + util-linux \ && rm -rf /var/lib/apt/lists/* COPY --from=build /out/bin/cartesi-machine /usr/local/bin/cartesi-machine @@ -86,16 +82,15 @@ COPY watchdog /opt/watchdog/lua/watchdog COPY watchdog/third_party/json.lua /opt/watchdog/lua/watchdog/third_party/json.lua # Shared cross-stack fixtures (watchdog/tests/run.lua expects repo-root tests/fixtures/). COPY tests/fixtures /opt/watchdog/lua/tests/fixtures -COPY watchdog/docker-entrypoint.sh /usr/local/bin/watchdog +COPY watchdog/sequencer-watchdog /usr/local/bin/sequencer-watchdog -RUN chmod +x /usr/local/bin/watchdog /usr/local/bin/cartesi-machine \ +RUN chmod +x /usr/local/bin/sequencer-watchdog /usr/local/bin/cartesi-machine \ && printf '%s\n' \ "{" \ " \"component\": \"sequencer-watchdog\"," \ " \"release_tag\": \"${RELEASE_TAG}\"," \ " \"git_commit\": \"${GIT_COMMIT}\"," \ - " \"cartesi_machine_version\": \"${CARTESI_MACHINE_VERSION}\"," \ - " \"lua_curl_upstream_sha\": \"${LUA_CURL_UPSTREAM_SHA}\"" \ + " \"cartesi_machine_version\": \"${CARTESI_MACHINE_VERSION}\"" \ "}" > /opt/watchdog/RELEASE.json ENV WATCHDOG_LUA_DEPS=/opt/watchdog/lib \ @@ -105,5 +100,5 @@ ENV WATCHDOG_LUA_DEPS=/opt/watchdog/lib \ PATH="/usr/local/share/cartesi-machine:${PATH}" WORKDIR /opt/watchdog -ENTRYPOINT ["/usr/local/bin/watchdog"] +ENTRYPOINT ["/usr/local/bin/sequencer-watchdog"] CMD [] diff --git a/watchdog/checkpoint.lua b/watchdog/checkpoint.lua index 2cb43cc..b89141f 100644 --- a/watchdog/checkpoint.lua +++ b/watchdog/checkpoint.lua @@ -3,6 +3,8 @@ local checkpoint = {} +checkpoint.HEAD_FILE = "head.json" + local function join(...) local parts = { ... } return table.concat(parts, "/"):gsub("//+", "/") @@ -23,11 +25,23 @@ local function write_all(path, data) if not file then return nil, err end - file:write(data) - file:close() + local ok, write_err = file:write(data) + if not ok then + file:close() + return nil, write_err + end + ok, err = file:close() + if not ok then + return nil, err + end return true end +local function path_exists(path) + local ok, _, code = os.rename(path, path) + return ok or code == 13 +end + local function shell_quote(value) value = tostring(value) return "'" .. value:gsub("'", "'\\''") .. "'" @@ -56,6 +70,28 @@ local function parse_pointer(data) return data:match('"checkpoint"%s*:%s*"([^"]+)"') end +local function validate_pointer(relative_path) + if type(relative_path) ~= "string" or not relative_path:match("^checkpoints/%d+$") then + return nil, "invalid checkpoint pointer" + end + return relative_path +end + +-- Relative path head.json points at (e.g. "checkpoints/000...019"), or nil +-- if there is no pointer yet. Used by write() to find the checkpoint it +-- supersedes without enumerating the directory. +local function read_pointer(dir) + local data = read_all(join(dir, checkpoint.HEAD_FILE)) + if not data then + return nil + end + return validate_pointer(parse_pointer(data)) +end + +local function relative_checkpoint_path(safe_block) + return join("checkpoints", string.format("%020d", safe_block)) +end + function checkpoint.safe_block_from_manifest(manifest_data) local value = tostring(manifest_data or ""):match('"safe_block"%s*:%s*(%d+)') if not value then @@ -65,13 +101,20 @@ function checkpoint.safe_block_from_manifest(manifest_data) end function checkpoint.load(dir) - local pointer_data, err = read_all(join(dir, "current.json")) + local pointer_path = join(dir, checkpoint.HEAD_FILE) + if not path_exists(pointer_path) then + return nil, "missing " .. checkpoint.HEAD_FILE + end + + local pointer_data, err = read_all(pointer_path) if not pointer_data then return nil, err end local relative_path = parse_pointer(pointer_data) + local pointer_err + relative_path, pointer_err = validate_pointer(relative_path) if not relative_path then - return nil, "invalid checkpoint pointer" + return nil, pointer_err end local checkpoint_dir = join(dir, relative_path) local manifest, manifest_err = read_all(join(checkpoint_dir, "manifest.json")) @@ -93,8 +136,7 @@ end function checkpoint.prepare(dir, safe_block) assert(type(safe_block) == "number", "safe_block must be a number") - local name = string.format("%020d", safe_block) - local relative_path = join("checkpoints", name) + local relative_path = relative_checkpoint_path(safe_block) local full_path = join(dir, relative_path) local snapshot_dir = join(full_path, "snapshot") @@ -127,12 +169,12 @@ function checkpoint.commit_prepared(dir, prepared, safe_block, manifest) return nil, err end - local tmp_pointer = join(dir, "current.json.tmp") + local tmp_pointer = join(dir, checkpoint.HEAD_FILE .. ".tmp") ok, err = write_all(tmp_pointer, pointer_json(prepared.relative_path)) if not ok then return nil, err end - ok, err = os.rename(tmp_pointer, join(dir, "current.json")) + ok, err = os.rename(tmp_pointer, join(dir, checkpoint.HEAD_FILE)) if not ok then return nil, err end @@ -145,6 +187,12 @@ end function checkpoint.write(dir, safe_block, snapshot_writer, manifest) assert(type(snapshot_writer) == "function", "snapshot_writer must be a function") + local relative_path = relative_checkpoint_path(safe_block) + local superseded = read_pointer(dir) + if superseded == relative_path then + return nil, "refusing to rewrite selected checkpoint: " .. relative_path + end + local prepared, prepare_err = checkpoint.prepare(dir, safe_block) if not prepared then return nil, prepare_err @@ -153,7 +201,24 @@ function checkpoint.write(dir, safe_block, snapshot_writer, manifest) if not ok then return nil, err end - return checkpoint.commit_prepared(dir, prepared, safe_block, manifest) + + -- The checkpoint head.json points at before the flip is the one we are + -- superseding (nil on the first write). + + local committed, commit_err = checkpoint.commit_prepared(dir, prepared, safe_block, manifest) + if not committed then + return nil, commit_err + end + + -- Reclaim the superseded checkpoint AFTER the atomic pointer flip, so + -- head.json always names a complete checkpoint (a crash here leaves at + -- most one stale dir, never an orphaned pointer). Best-effort: GC must not + -- fail a cycle whose compare and checkpoint write already succeeded. + if superseded and superseded ~= prepared.relative_path then + os.execute("rm -rf " .. shell_quote(join(dir, superseded))) + end + + return committed end return checkpoint diff --git a/watchdog/config.lua b/watchdog/config.lua index e5f47e0..76c694d 100644 --- a/watchdog/config.lua +++ b/watchdog/config.lua @@ -3,6 +3,8 @@ local config = {} +config.VERSION = 1 + local function required(name, env) local value = env[name] if value == nil or value == "" then @@ -42,7 +44,7 @@ local function split_csv(value) return out end -function config.load(env) +local function normalize_env(env) env = env or os.getenv if type(env) == "function" then local getenv = env @@ -52,37 +54,94 @@ function config.load(env) end, }) end + return env +end - local mode = env.WATCHDOG_MODE or "advance" - if mode ~= "advance" and mode ~= "compare" then - error("WATCHDOG_MODE must be 'advance' or 'compare'") - end +function config.load_state_dir(env) + env = normalize_env(env) + return required("WATCHDOG_STATE_DIR", env) +end +function config.load_init(env) + env = normalize_env(env) + + -- The watchdog has one job: compare the sequencer's finalized state against + -- a canonical CM re-derivation. One cycle per process; infra schedules it. return { - mode = mode, - sequencer_url = mode == "compare" and required("WATCHDOG_SEQUENCER_URL", env) or env.WATCHDOG_SEQUENCER_URL, - l1_rpc_url = required("WATCHDOG_L1_RPC_URL", env), + version = config.VERSION, + sequencer_url = required("WATCHDOG_SEQUENCER_URL", env), input_box_address = required("WATCHDOG_INPUTBOX_ADDRESS", env), app_address = required("WATCHDOG_APP_ADDRESS", env), input_added_topic = env.WATCHDOG_INPUT_ADDED_TOPIC, - checkpoint_dir = required("WATCHDOG_CHECKPOINT_DIR", env), - cm_snapshot_dir = env.WATCHDOG_CM_SNAPSHOT_DIR, + state_dir = required("WATCHDOG_STATE_DIR", env), + cm_snapshot_dir = required("WATCHDOG_CM_SNAPSHOT_DIR", env), cm_snapshot_safe_block = optional_required_number( "WATCHDOG_CM_SNAPSHOT_DIR", "WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK", env ), - cm_work_dir = env.WATCHDOG_CM_WORK_DIR or "/tmp", - cm_executable = env.WATCHDOG_CM_EXECUTABLE or "cartesi-machine", - target_safe_block = optional_number("WATCHDOG_TARGET_SAFE_BLOCK", nil, env), - poll_interval_sec = optional_number("WATCHDOG_POLL_INTERVAL_SEC", 30, env), + cm_image_hash = env.WATCHDOG_CM_IMAGE_HASH, retry_attempts = optional_number("WATCHDOG_RETRY_ATTEMPTS", 3, env), retry_delay_sec = optional_number("WATCHDOG_RETRY_DELAY_SEC", 5, env), - once = env.WATCHDOG_ONCE == "1", long_block_range_error_codes = split_csv( env.WATCHDOG_LONG_BLOCK_RANGE_ERROR_CODES or "-32005,-32600,-32602,-32616" ), } end +local function required_field(data, name) + local value = data[name] + if value == nil or value == "" then + error("config.json missing " .. name) + end + return value +end + +local function optional_field_number(data, name, default) + local value = data[name] + if value == nil then + return default + end + if type(value) ~= "number" then + error("config.json " .. name .. " must be a number") + end + return value +end + +function config.persisted(cfg) + return { + version = config.VERSION, + sequencer_url = cfg.sequencer_url, + input_box_address = cfg.input_box_address, + app_address = cfg.app_address, + input_added_topic = cfg.input_added_topic, + cm_image_hash = cfg.cm_image_hash, + retry_attempts = cfg.retry_attempts, + retry_delay_sec = cfg.retry_delay_sec, + long_block_range_error_codes = cfg.long_block_range_error_codes, + } +end + +function config.from_persisted(state_dir, data, env) + env = normalize_env(env) + if data.version ~= config.VERSION then + error("unsupported config.json version: " .. tostring(data.version)) + end + return { + version = config.VERSION, + state_dir = state_dir, + sequencer_url = required_field(data, "sequencer_url"), + l1_rpc_url = required("WATCHDOG_L1_RPC_URL", env), + input_box_address = required_field(data, "input_box_address"), + app_address = required_field(data, "app_address"), + input_added_topic = data.input_added_topic, + cm_image_hash = data.cm_image_hash, + retry_attempts = optional_field_number(data, "retry_attempts", 3), + retry_delay_sec = optional_field_number(data, "retry_delay_sec", 5), + long_block_range_error_codes = data.long_block_range_error_codes or {}, + } +end + +config.load = config.load_init + return config diff --git a/watchdog/docker-entrypoint.sh b/watchdog/docker-entrypoint.sh deleted file mode 100755 index dc5c27b..0000000 --- a/watchdog/docker-entrypoint.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -# Wrapper: run compare/advance loop with image-bundled Lua paths. -set -euo pipefail - -if [[ "${WATCHDOG_PRINT_RELEASE_INFO:-0}" == "1" ]]; then - cat /opt/watchdog/RELEASE.json || true - cartesi-machine --version 2>&1 | sed 's/^/cartesi-machine: /' || true - exit 0 -fi - -cd /opt/watchdog/lua -exec lua5.4 watchdog/main.lua "$@" diff --git a/watchdog/l1_reader.lua b/watchdog/l1_reader.lua index af0b461..e796f93 100644 --- a/watchdog/l1_reader.lua +++ b/watchdog/l1_reader.lua @@ -76,9 +76,10 @@ function l1_reader.ensure_rpc_head_at_least(rpc, target_block) return head end -function l1_reader.fetch_logs_partitioned(rpc, params) +local function scan_partitions(rpc, params, on_logs) assert(type(rpc) == "table" and type(rpc.get_logs) == "function", "rpc.get_logs is required") assert(type(params) == "table", "params are required") + assert(type(on_logs) == "function", "on_logs callback is required") local start_block = assert(params.start_block, "start_block is required") local end_block = assert(params.end_block, "end_block is required") @@ -94,37 +95,58 @@ function l1_reader.fetch_logs_partitioned(rpc, params) input_added_topic = input_added_topic, }) if logs then - return logs + l1_reader.sort_logs(logs) + return on_logs(logs, { + from_block = from_block, + to_block = to_block, + }) end if from_block < to_block and contains_any(err, codes) then local mid = from_block + ((to_block - from_block) // 2) - local left, left_err = go(from_block, mid) - if not left then + local left_count, left_err = go(from_block, mid) + if not left_count then return nil, left_err end - local right, right_err = go(mid + 1, to_block) - if not right then + local right_count, right_err = go(mid + 1, to_block) + if not right_count then return nil, right_err end - for _, log in ipairs(right) do - table.insert(left, log) - end - return left + return left_count + right_count end return nil, err end if end_block < start_block then - return {} + return 0 end - local logs, err = go(start_block, end_block) - if not logs then + return go(start_block, end_block) +end + +function l1_reader.for_each_log_chunk_partitioned(rpc, params, on_logs) + return scan_partitions(rpc, params, function(logs, range) + local ok, err = on_logs(logs, range) + if not ok then + return nil, err + end + return #logs + end) +end + +function l1_reader.fetch_logs_partitioned(rpc, params) + local all_logs = {} + local count, err = l1_reader.for_each_log_chunk_partitioned(rpc, params, function(logs) + for _, log in ipairs(logs) do + table.insert(all_logs, log) + end + return true + end) + if not count then return nil, err end - return l1_reader.sort_logs(logs) + return all_logs end function l1_reader.decode_and_validate_log(log) @@ -140,15 +162,32 @@ function l1_reader.decode_and_validate_log(log) return decoded end -function l1_reader.fetch_inputs(rpc, params) - local logs, err = l1_reader.fetch_logs_partitioned(rpc, params) - if not logs then - return nil, err - end +function l1_reader.for_each_input_chunk_partitioned(rpc, params, on_inputs) + assert(type(on_inputs) == "function", "on_inputs callback is required") + return scan_partitions(rpc, params, function(logs, range) + local inputs = {} + for _, log in ipairs(logs) do + table.insert(inputs, l1_reader.decode_and_validate_log(log)) + end + local ok, err = on_inputs(inputs, range) + if not ok then + return nil, err + end + return #inputs + end) +end + +function l1_reader.fetch_inputs(rpc, params) local inputs = {} - for _, log in ipairs(logs) do - table.insert(inputs, l1_reader.decode_and_validate_log(log)) + local count, err = l1_reader.for_each_input_chunk_partitioned(rpc, params, function(chunk) + for _, input in ipairs(chunk) do + table.insert(inputs, input) + end + return true + end) + if not count then + return nil, err end return inputs end diff --git a/watchdog/machine_cartesi.lua b/watchdog/machine_cartesi.lua index 70b9374..5de1382 100644 --- a/watchdog/machine_cartesi.lua +++ b/watchdog/machine_cartesi.lua @@ -158,9 +158,6 @@ function machine_cartesi.new(_opts) function binding.load_snapshot(snapshot_dir) assert(type(snapshot_dir) == "string" and snapshot_dir ~= "", "snapshot_dir is required") local ok, machine = pcall(load_machine, snapshot_dir, runtime_config) - if not ok then - ok, machine = pcall(load_machine, snapshot_dir, {}) - end if not ok then return nil, tostring(machine) end diff --git a/watchdog/main.lua b/watchdog/main.lua index e4696a2..a7ecd97 100644 --- a/watchdog/main.lua +++ b/watchdog/main.lua @@ -4,6 +4,7 @@ package.path = "./?.lua;./?/init.lua;" .. package.path local config = require("watchdog.config") +local checkpoint = require("watchdog.checkpoint") local http_mod = require("watchdog.http") local json_mod = require("watchdog.json") local jsonrpc = require("watchdog.jsonrpc") @@ -11,6 +12,7 @@ local machine_cartesi = require("watchdog.machine_cartesi") local retry = require("watchdog.retry") local runner = require("watchdog.runner") local sequencer_reader = require("watchdog.sequencer_reader") +local state = require("watchdog.state") local EXIT_OK = 0 local EXIT_TRANSIENT = 1 @@ -27,11 +29,8 @@ local function load_machine(_cfg) return machine_cartesi.new() end -local function run_once(cfg, deps) - if cfg.mode == "compare" then - return runner.run_once(cfg, deps) - end - return runner.advance_checkpoint_once(cfg, deps) +local function log_step(message) + io.stderr:write("watchdog_step " .. tostring(message) .. "\n") end local function is_table_with_kind(value) @@ -44,7 +43,6 @@ local function is_terminal_error(value) end return value.kind == "state_mismatch" or value.kind == "inclusion_block_regressed" - or value.kind == "safe_block_regressed" end local function emit_watchdog_event(json, payload, deps) @@ -54,28 +52,83 @@ local function emit_watchdog_event(json, payload, deps) io.stderr:write("watchdog_event " .. json.encode(payload) .. "\n") end +local function default_machine_deps(cfg) + return { + machine = load_machine(cfg), + log_step = log_step, + } +end + local function default_deps(cfg) local http = http_mod.new() local json = json_mod.new() - local deps = { - http = http, - rpc = jsonrpc.new(http, json, cfg.l1_rpc_url), - machine = load_machine(cfg), - log_step = function(message) - io.stderr:write("watchdog_step " .. tostring(message) .. "\n") - end, + local deps = default_machine_deps(cfg) + deps.http = http + deps.rpc = jsonrpc.new(http, json, cfg.l1_rpc_url) + deps.sequencer = sequencer_reader.new(http, json, cfg.sequencer_url) + return deps, json +end + +local function run_init(cfg, deps) + deps = deps or default_machine_deps(cfg) + local json = json_mod.new() + + local existing, load_err = checkpoint.load(cfg.state_dir) + if existing then + return nil, "watchdog state already initialized" + end + if load_err ~= "missing " .. checkpoint.HEAD_FILE then + return nil, "failed to load watchdog head: " .. tostring(load_err) + end + + local ok, err = state.write_json_atomic(cfg.state_dir, "config.json", config.persisted(cfg), json) + if not ok then + return nil, err + end + + local machine = deps.machine or load_machine(cfg) + if type(deps.log_step) == "function" then + deps.log_step("load bootstrap CM snapshot") + end + local instance, load_snapshot_err = machine:load(cfg.cm_snapshot_dir, cfg.cm_snapshot_safe_block) + if not instance then + return nil, load_snapshot_err + end + + if type(deps.log_step) == "function" then + deps.log_step("persist initial watchdog checkpoint") + end + local written, write_err = checkpoint.write(cfg.state_dir, cfg.cm_snapshot_safe_block, function(snapshot_dir) + return machine:dump(instance, snapshot_dir, cfg.cm_snapshot_safe_block) + end, { + created_at = os.date("!%Y-%m-%dT%H:%M:%SZ"), + cm_image_hash = cfg.cm_image_hash, + }) + if not written then + return nil, write_err + end + + return { + ok = true, + safe_block = cfg.cm_snapshot_safe_block, } - if cfg.sequencer_url then - deps.sequencer = sequencer_reader.new(http, json, cfg.sequencer_url) +end + +local function load_tick_config(env) + local json = json_mod.new() + local state_dir = config.load_state_dir(env) + local persisted, err = state.read_json(state_dir, "config.json", json) + if not persisted then + error("failed to load config.json: " .. tostring(err)) end - return deps, json + return config.from_persisted(state_dir, persisted, env) end local function run_compare_cycle(cfg, deps) deps = deps or select(1, default_deps(cfg)) local json = json_mod.new() local result, err = retry.with_retries(function() - local ok, value, run_err = pcall(run_once, cfg, deps) + local ok, value, run_err = pcall(runner.run_once, cfg, deps) if not ok then return nil, value end @@ -97,34 +150,99 @@ local function run_compare_cycle(cfg, deps) return EXIT_OK, result end -local function main() +local function run_tick(cfg, deps) + return run_compare_cycle(cfg, deps) +end + +local function usage() + return "usage: watchdog " +end + +local function load_or_error(loader) + local ok, value = pcall(loader) + if not ok then + return nil, value + end + return value +end + +local function exit_for_result(command, exit_code, err) + if exit_code == EXIT_DIVERGENCE then + os.exit(EXIT_DIVERGENCE) + end + if exit_code == EXIT_TRANSIENT then + io.stderr:write("watchdog " .. command .. " failed: " .. tostring(err) .. "\n") + os.exit(EXIT_TRANSIENT) + end + os.exit(EXIT_OK) +end + +-- One compare cycle per `tick` process. Infra (systemd timer / k8s CronJob) +-- schedules re-runs and reacts to the exit code; the watchdog itself does not loop. +local function main(argv) + argv = argv or arg or {} prepend_deps_cpath() - local cfg = config.load() - repeat - local exit_code, err = run_compare_cycle(cfg) - if exit_code == EXIT_DIVERGENCE then - os.exit(EXIT_DIVERGENCE) + local command = argv[1] + if command == "init" then + local cfg, cfg_err = load_or_error(function() + return config.load_init() + end) + if not cfg then + io.stderr:write("watchdog init failed: " .. tostring(cfg_err) .. "\n") + os.exit(EXIT_TRANSIENT) end - if exit_code == EXIT_TRANSIENT then - io.stderr:write("watchdog run failed: " .. tostring(err) .. "\n") + local ok, result, err = pcall(run_init, cfg) + if not ok then + io.stderr:write("watchdog init failed: " .. tostring(result) .. "\n") os.exit(EXIT_TRANSIENT) end - if cfg.once then - os.exit(EXIT_OK) + if not result then + io.stderr:write("watchdog init failed: " .. tostring(err) .. "\n") + os.exit(EXIT_TRANSIENT) end - os.execute("sleep " .. tostring(cfg.poll_interval_sec)) - until false + os.exit(EXIT_OK) + end + + if command == "tick" then + local cfg, cfg_err = load_or_error(function() + return load_tick_config() + end) + if not cfg then + io.stderr:write("watchdog tick failed: " .. tostring(cfg_err) .. "\n") + os.exit(EXIT_TRANSIENT) + end + local ok, exit_code, err = pcall(run_tick, cfg) + if not ok then + io.stderr:write("watchdog tick failed: " .. tostring(exit_code) .. "\n") + os.exit(EXIT_TRANSIENT) + end + if not exit_code then + exit_code = EXIT_TRANSIENT + end + exit_for_result(command, exit_code, err) + end + + io.stderr:write(usage() .. "\n") + os.exit(EXIT_TRANSIENT) +end + +local function invoked_as_script() + return type(arg) == "table" + and type(arg[0]) == "string" + and arg[0]:match("watchdog[/\\]main%.lua$") ~= nil end -if ... == nil then - main() +if invoked_as_script() then + main(arg) end return { main = main, - run_once = run_once, + run_init = run_init, + run_tick = run_tick, run_compare_cycle = run_compare_cycle, + load_tick_config = load_tick_config, EXIT_OK = EXIT_OK, EXIT_TRANSIENT = EXIT_TRANSIENT, EXIT_DIVERGENCE = EXIT_DIVERGENCE, diff --git a/watchdog/retry.lua b/watchdog/retry.lua index e167ca0..c4c09a0 100644 --- a/watchdog/retry.lua +++ b/watchdog/retry.lua @@ -3,6 +3,17 @@ local retry = {} +local function default_sleep(seconds) + seconds = tonumber(seconds) + if not seconds or seconds <= 0 then + return + end + local ok, how, code = os.execute("exec sleep " .. tostring(seconds)) + if not ok and how == "signal" and code == 2 then + os.exit(130, true) + end +end + function retry.with_retries(fn, opts) opts = opts or {} local attempts = math.max(1, opts.attempts or 1) @@ -10,11 +21,7 @@ function retry.with_retries(fn, opts) local should_retry = opts.should_retry or function(_err) return true end - local sleep = opts.sleep or function(seconds) - if seconds > 0 then - os.execute("sleep " .. tostring(seconds)) - end - end + local sleep = opts.sleep or default_sleep local last_err for attempt = 1, attempts do diff --git a/watchdog/runner.lua b/watchdog/runner.lua index 5881c6a..393c67a 100644 --- a/watchdog/runner.lua +++ b/watchdog/runner.lua @@ -14,26 +14,15 @@ local function require_dep(deps, name) end local function load_checkpoint(cfg, checkpoint_mod) - local loaded = checkpoint_mod.load(cfg.checkpoint_dir) + local loaded, load_err = checkpoint_mod.load(cfg.state_dir) if loaded then return loaded end - - if not cfg.cm_snapshot_dir or cfg.cm_snapshot_dir == "" then - error("no checkpoint found and WATCHDOG_CM_SNAPSHOT_DIR is not configured") - end - if type(cfg.cm_snapshot_safe_block) ~= "number" then - error("no checkpoint found and WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK is not configured") - end - - return { - snapshot_dir = cfg.cm_snapshot_dir, - safe_block = cfg.cm_snapshot_safe_block, - } + error("failed to load watchdog head: " .. tostring(load_err)) end local function ensure_rpc_head_covers_target(deps, target_block) - if deps.fetch_inputs then + if deps.fetch_inputs or deps.for_each_input_chunk then return true end @@ -49,163 +38,152 @@ local function ensure_rpc_head_covers_target(deps, target_block) return true end -local function fetch_inputs(cfg, deps, from_block, to_block) - if from_block > to_block then - return {} - end - - if deps.fetch_inputs then - return deps.fetch_inputs(from_block, to_block) - end - - local rpc = require_dep(deps, "rpc") - return l1_reader.fetch_inputs(rpc, { +local function l1_params(cfg, from_block, to_block) + return { start_block = from_block, end_block = to_block, input_box_address = cfg.input_box_address, app_address = cfg.app_address, input_added_topic = cfg.input_added_topic, long_block_range_error_codes = cfg.long_block_range_error_codes, - }) + } end -local function step(deps, message) - if deps and type(deps.log_step) == "function" then - deps.log_step(message) +local function for_each_input_chunk(cfg, deps, from_block, to_block, on_chunk) + if from_block > to_block then + return 0 end -end -local function target_safe_block(cfg, deps) - if type(cfg.target_safe_block) == "number" then - return cfg.target_safe_block + if deps.for_each_input_chunk then + return deps.for_each_input_chunk(from_block, to_block, on_chunk) end - if deps.safe_block then - return deps.safe_block() + + if deps.fetch_inputs then + local inputs, err = deps.fetch_inputs(from_block, to_block) + if not inputs then + return nil, err + end + local ok, callback_err = on_chunk(inputs, { + from_block = from_block, + to_block = to_block, + }) + if not ok then + return nil, callback_err + end + return #inputs end - if deps.rpc and type(deps.rpc.get_block_number_by_tag) == "function" then - return deps.rpc:get_block_number_by_tag("safe") + + local rpc = require_dep(deps, "rpc") + return l1_reader.for_each_input_chunk_partitioned(rpc, l1_params(cfg, from_block, to_block), on_chunk) +end + +local function step(deps, message) + if deps and type(deps.log_step) == "function" then + deps.log_step(message) end - return nil, "target safe block is not configured" end -local function advance_compare_and_checkpoint( - cfg, - deps, - loaded, - inputs, - safe_block_prev, - safe_block_next, - with_compare -) +local function compare_and_checkpoint(cfg, deps, instance, safe_block_prev, safe_block_next, input_count) local checkpoint_mod = deps.checkpoint or checkpoint local machine = require_dep(deps, "machine") - step(deps, "load CM snapshot directory") - local instance, load_err = machine:load(loaded.snapshot_dir, safe_block_prev) - if not instance then - return nil, load_err + step(deps, "run CM inspect (state query)") + local cm_state, inspect_err = machine:inspect(instance) + if not cm_state then + return nil, inspect_err end - if safe_block_next > safe_block_prev then - step(deps, string.format("advance CM for blocks %d..%d", safe_block_prev + 1, safe_block_next)) - local advanced, advance_err = machine:advance(instance, inputs, { - from_block = safe_block_prev + 1, - to_block = safe_block_next, - }) - if not advanced then - return nil, advance_err - end - else - instance.reference_block = safe_block_prev + local sequencer = require_dep(deps, "sequencer") + step(deps, "fetch sequencer GET /finalized_state") + local finalized, state_err = sequencer:get_finalized_state() + if not finalized then + return nil, state_err + end + if finalized.not_modified then + return nil, "finalized state unexpectedly returned 304 during compare" + end + if finalized.inclusion_block ~= safe_block_next then + return nil, string.format( + "finalized inclusion_block moved during compare cycle (%s -> %s); retry", + tostring(safe_block_next), + tostring(finalized.inclusion_block) + ) end - if with_compare then - step(deps, "run CM inspect (state query)") - local cm_state, inspect_err = machine:inspect(instance) - if not cm_state then - return nil, inspect_err - end - - local sequencer = require_dep(deps, "sequencer") - step(deps, "fetch sequencer GET /finalized_state") - local finalized, state_err = sequencer:get_finalized_state() - if not finalized then - return nil, state_err - end - if finalized.not_modified then - return nil, "finalized state unexpectedly returned 304 during compare" - end - if finalized.inclusion_block ~= safe_block_next then - return nil, string.format( - "finalized inclusion_block moved during compare cycle (%s -> %s); retry", - tostring(safe_block_next), - tostring(finalized.inclusion_block) - ) - end - - step(deps, "compare finalized SSZ bytes against CM inspect report") - local equal, mismatch_offset = compare.raw_equal(finalized.state, cm_state) - if not equal then - return nil, { - kind = "state_mismatch", - previous_safe_block = safe_block_prev, - sequencer_inclusion_block = finalized.inclusion_block, - mismatch_offset = mismatch_offset, - } - end + step(deps, "compare finalized SSZ bytes against CM inspect report") + local equal, mismatch_offset = compare.raw_equal(finalized.state, cm_state) + if not equal then + return nil, { + kind = "state_mismatch", + previous_safe_block = safe_block_prev, + sequencer_inclusion_block = finalized.inclusion_block, + mismatch_offset = mismatch_offset, + } end - if safe_block_next > safe_block_prev then - step(deps, "persist new manifest-backed checkpoint") - local written, write_err = checkpoint_mod.write(cfg.checkpoint_dir, safe_block_next, function(snapshot_dir) - return machine:dump(instance, snapshot_dir, safe_block_next) - end, { - created_at = os.date("!%Y-%m-%dT%H:%M:%SZ"), - cm_image_hash = cfg.cm_image_hash, - }) - if not written then - return nil, write_err - end - else - step(deps, "reference block unchanged; skip checkpoint write") + -- Compare succeeded: persist a fresh block-named checkpoint before flipping + -- head.json. The previous checkpoint is pruned after the pointer flip. + step(deps, "persist new manifest-backed checkpoint") + local written, write_err = checkpoint_mod.write(cfg.state_dir, safe_block_next, function(snapshot_dir) + return machine:dump(instance, snapshot_dir, safe_block_next) + end, { + created_at = os.date("!%Y-%m-%dT%H:%M:%SZ"), + cm_image_hash = cfg.cm_image_hash, + }) + if not written then + return nil, write_err end return { ok = true, previous_safe_block = safe_block_prev, safe_block = safe_block_next, - input_count = #inputs, + input_count = input_count, } end ---- Shared path: L1 fetch → CM advance/inspect/compare → optional checkpoint write. -local function run_pass(cfg, deps, loaded, safe_block_prev, safe_block_next, with_compare) +--- L1 fetch → CM advance → inspect → compare → checkpoint. +local function run_pass(cfg, deps, loaded, safe_block_prev, safe_block_next) step(deps, string.format("check L1 RPC head covers target block %d", safe_block_next)) local head_ok, head_err = ensure_rpc_head_covers_target(deps, safe_block_next) if not head_ok then return nil, head_err end + local machine = require_dep(deps, "machine") + step(deps, "load CM snapshot directory") + local instance, load_err = machine:load(loaded.snapshot_dir, safe_block_prev) + if not instance then + return nil, load_err + end + step(deps, string.format( - "fetch L1 InputAdded logs for blocks %s..%s", + "stream L1 InputAdded logs for blocks %s..%s", tostring(safe_block_prev + 1), tostring(safe_block_next) )) - local inputs, input_err = fetch_inputs(cfg, deps, safe_block_prev + 1, safe_block_next) - if not inputs then - return nil, input_err - end - - step(deps, string.format("feed %d decoded inputs into CM", #inputs)) - return advance_compare_and_checkpoint( + local input_count = 0 + local advanced_count, input_err = for_each_input_chunk( cfg, deps, - loaded, - inputs, - safe_block_prev, + safe_block_prev + 1, safe_block_next, - with_compare + function(inputs, range) + input_count = input_count + #inputs + step(deps, string.format( + "feed %d decoded inputs into CM for blocks %d..%d", + #inputs, + range.from_block, + range.to_block + )) + return machine:advance(instance, inputs, range) + end ) + if not advanced_count then + return nil, input_err + end + + return compare_and_checkpoint(cfg, deps, instance, safe_block_prev, safe_block_next, input_count) end local function skip_result(safe_block_prev, safe_block_next, reason) @@ -223,7 +201,7 @@ function runner.run_once(cfg, deps) deps = deps or {} local checkpoint_mod = deps.checkpoint or checkpoint - step(deps, "load checkpoint or bootstrap CM snapshot") + step(deps, "load watchdog checkpoint") local loaded = load_checkpoint(cfg, checkpoint_mod) local safe_block_prev = loaded.safe_block or 0 @@ -248,55 +226,16 @@ function runner.run_once(cfg, deps) } end - if safe_block_next <= safe_block_prev then + if safe_block_next == safe_block_prev then step(deps, "finalized inclusion_block unchanged; skip L1/CM/compare cycle") return skip_result(safe_block_prev, safe_block_next, "finalized_unchanged") end - local result, err = run_pass(cfg, deps, loaded, safe_block_prev, safe_block_next, true) + local result, err = run_pass(cfg, deps, loaded, safe_block_prev, safe_block_next) if result then step(deps, "compare pass complete") end return result, err end -function runner.advance_checkpoint_once(cfg, deps) - deps = deps or {} - local checkpoint_mod = deps.checkpoint or checkpoint - - step(deps, "load checkpoint or bootstrap CM snapshot") - local loaded = load_checkpoint(cfg, checkpoint_mod) - local safe_block_prev = loaded.safe_block or 0 - - step(deps, "resolve target safe block") - local safe_block_next, safe_err = target_safe_block(cfg, deps) - if not safe_block_next then - return nil, safe_err - end - - step(deps, string.format( - "check safe_block monotonicity (prev=%s next=%s)", - tostring(safe_block_prev), - tostring(safe_block_next) - )) - if safe_block_next < safe_block_prev then - return nil, { - kind = "safe_block_regressed", - previous_safe_block = safe_block_prev, - safe_block = safe_block_next, - } - end - - if safe_block_next <= safe_block_prev then - step(deps, "L1 safe block unchanged; skip advance cycle") - return skip_result(safe_block_prev, safe_block_next, "safe_unchanged") - end - - local result, err = run_pass(cfg, deps, loaded, safe_block_prev, safe_block_next, false) - if result then - step(deps, "advance pass complete") - end - return result, err -end - return runner diff --git a/watchdog/sequencer-watchdog b/watchdog/sequencer-watchdog new file mode 100755 index 0000000..285729c --- /dev/null +++ b/watchdog/sequencer-watchdog @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Production CLI wrapper for watchdog subcommands. +set -euo pipefail + +if [[ "${WATCHDOG_PRINT_RELEASE_INFO:-0}" == "1" ]]; then + cat /opt/watchdog/RELEASE.json || true + cartesi-machine --version 2>&1 | sed 's/^/cartesi-machine: /' || true + exit 0 +fi + +lua_root="${WATCHDOG_LUA_ROOT:-/opt/watchdog/lua}" +lua_bin="${WATCHDOG_LUA_BIN:-lua5.4}" + +cd "${lua_root}" + +if [[ "$#" -gt 0 && ( "$1" == "init" || "$1" == "tick" ) ]]; then + : "${WATCHDOG_STATE_DIR:?WATCHDOG_STATE_DIR is required}" + mkdir -p "$WATCHDOG_STATE_DIR" + exec 9>"$WATCHDOG_STATE_DIR/run.lock" + if ! flock -n 9; then + echo "watchdog state is already locked: $WATCHDOG_STATE_DIR/run.lock" >&2 + exit 1 + fi +fi + +exec "$lua_bin" watchdog/main.lua "$@" diff --git a/watchdog/state.lua b/watchdog/state.lua new file mode 100644 index 0000000..ae17c48 --- /dev/null +++ b/watchdog/state.lua @@ -0,0 +1,98 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local state = {} + +local function join(...) + local parts = { ... } + return table.concat(parts, "/"):gsub("//+", "/") +end + +local function shell_quote(value) + value = tostring(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +local function read_all(path) + local file, err = io.open(path, "rb") + if not file then + return nil, err + end + local data = file:read("*a") + file:close() + return data +end + +local function write_all(path, data) + local file, err = io.open(path, "wb") + if not file then + return nil, err + end + local ok, write_err = file:write(data) + if not ok then + file:close() + return nil, write_err + end + ok, err = file:close() + if not ok then + return nil, err + end + return true +end + +local function mkdir_p(path) + local ok = os.execute("mkdir -p " .. shell_quote(path)) + if ok ~= true and ok ~= 0 then + return nil, "mkdir failed: " .. path + end + return true +end + +function state.ensure_dir(dir) + assert(type(dir) == "string" and dir ~= "", "state dir is required") + return mkdir_p(dir) +end + +function state.write_json_atomic(dir, name, value, json) + assert(type(dir) == "string" and dir ~= "", "state dir is required") + assert(type(name) == "string" and name ~= "", "file name is required") + assert(type(json) == "table" and type(json.encode) == "function", "json encoder is required") + + local ok, err = state.ensure_dir(dir) + if not ok then + return nil, err + end + + local path = join(dir, name) + local tmp = path .. ".tmp" + ok, err = write_all(tmp, json.encode(value) .. "\n") + if not ok then + return nil, err + end + ok, err = os.rename(tmp, path) + if not ok then + return nil, err + end + return true +end + +function state.read_json(dir, name, json) + assert(type(dir) == "string" and dir ~= "", "state dir is required") + assert(type(name) == "string" and name ~= "", "file name is required") + assert(type(json) == "table" and type(json.decode) == "function", "json decoder is required") + + local data, err = read_all(join(dir, name)) + if not data then + return nil, err + end + local decoded + local ok = pcall(function() + decoded = json.decode(data) + end) + if not ok or type(decoded) ~= "table" then + return nil, "invalid " .. name + end + return decoded +end + +return state diff --git a/watchdog/tests/drill_divergence.lua b/watchdog/tests/drill_divergence.lua index e195a47..b47bc63 100644 --- a/watchdog/tests/drill_divergence.lua +++ b/watchdog/tests/drill_divergence.lua @@ -19,8 +19,7 @@ local log = dofile("watchdog/tests/e2e_log.lua") local function fake_cfg() return { - mode = "compare", - checkpoint_dir = "/tmp/watchdog-drill", + state_dir = "/tmp/watchdog-drill", cm_snapshot_dir = "/tmp/genesis-snapshot", cm_snapshot_safe_block = 0, input_box_address = "0xinputbox", diff --git a/watchdog/tests/e2e.lua b/watchdog/tests/e2e.lua index 51726aa..8322de7 100644 --- a/watchdog/tests/e2e.lua +++ b/watchdog/tests/e2e.lua @@ -45,9 +45,11 @@ local function path_is_dir(path) end local function temp_dir(prefix) + -- Keep os.tmpname()'s full path (under the system temp dir) so scratch dirs + -- land in TMPDIR, not the repo root. Stripping the dir left them in cwd. local base = os.tmpname() os.remove(base) - local dir = string.format("%s-%s", prefix, base:match("([^/]+)$")) + local dir = string.format("%s-%s", base, prefix) local ok = os.execute('mkdir -p "' .. dir .. '"') if ok ~= true and ok ~= 0 then error("mkdir failed for " .. dir) @@ -83,55 +85,43 @@ local function skip(scenario, reason) return "skip" end -local function require_cartesi_machine(scenario) +-- Toolchain prerequisites are hard requirements, not skips: in CI a missing +-- dep must fail the run, never pass vacuously. (The genuinely-optional +-- live-sequencer scenario still skips on its own, below.) +local function require_cartesi_machine() if not command_exists("cartesi-machine") then - return skip(scenario, "cartesi-machine not on PATH (install via nix develop / Cartesi tools)") + error("cartesi-machine not on PATH (install via nix develop / Cartesi tools)", 0) end - return true end -local function require_machine_image(scenario) +local function require_machine_image() if not path_is_dir(MACHINE_IMAGE) then - return skip( - scenario, - "canonical machine image missing at " - .. MACHINE_IMAGE - .. " (run: just canonical-build-machine-image)" + error( + "canonical machine image missing at " .. MACHINE_IMAGE .. " (run: just canonical-build-machine-image)", + 0 ) end - return true end -local function require_machine_cartesi_binding(scenario) - if machine_cartesi_probe ~= nil then - if machine_cartesi_probe.ok then - return true +-- A missing in-process cartesi binding is the exact failure that must not pass +-- silently in CI (the prerequisites scenario does not cover it). Probe is +-- cached so the machine is only loaded once. +local function require_machine_cartesi_binding() + if machine_cartesi_probe == nil then + local machine = machine_cartesi.new() + local instance, err = machine:load(MACHINE_IMAGE) + if instance then + machine_cartesi_probe = { ok = true } + else + machine_cartesi_probe = { + ok = false, + err = "machine_cartesi binding unavailable on this host: " .. tostring(err), + } end - return skip(scenario, machine_cartesi_probe.err) end - local machine = machine_cartesi.new() - local instance, err = machine:load(MACHINE_IMAGE) - if not instance then - machine_cartesi_probe = { - ok = false, - err = "machine_cartesi binding unavailable on this host: " .. tostring(err), - } - return skip(scenario, machine_cartesi_probe.err) + if not machine_cartesi_probe.ok then + error(machine_cartesi_probe.err, 0) end - machine_cartesi_probe = { ok = true } - return true -end - -local function advance_cfg(checkpoint_dir, target_safe_block) - return { - mode = "advance", - checkpoint_dir = checkpoint_dir, - cm_snapshot_dir = MACHINE_IMAGE, - cm_snapshot_safe_block = GENESIS_SAFE_BLOCK, - target_safe_block = target_safe_block, - cm_executable = "cartesi-machine", - cm_work_dir = temp_dir("watchdog-e2e-work"), - } end table.insert(scenarios, { @@ -152,62 +142,13 @@ table.insert(scenarios, { end, }) -table.insert(scenarios, { - name = "advance-empty-range", - fn = function() - local scenario = "advance-empty-range" - if require_cartesi_machine(scenario) == "skip" then - return "skip" - end - if require_machine_image(scenario) == "skip" then - return "skip" - end - if require_machine_cartesi_binding(scenario) == "skip" then - return "skip" - end - - local checkpoint_dir = temp_dir("watchdog-e2e-checkpoint") - local target_safe_block = 1 - log.step(scenario, 1, 5, "prepare temp checkpoint dir: " .. checkpoint_dir) - log.step(scenario, 2, 5, "bootstrap from genesis snapshot at safe_block=" .. GENESIS_SAFE_BLOCK) - log.step(scenario, 3, 5, "run advance mode with empty L1 input range 1.." .. target_safe_block) - local result, err = runner.advance_checkpoint_once(advance_cfg(checkpoint_dir, target_safe_block), { - machine = machine_cartesi.new(), - log_step = make_step_logger(scenario .. "/runner", 8), - fetch_inputs = function(from_block, to_block) - assert_true(from_block == 1, "expected from_block=1") - assert_true(to_block == target_safe_block, "expected to_block=" .. target_safe_block) - return {} - end, - }) - assert_true(result, "advance failed: " .. tostring(err)) - assert_true(result.safe_block == target_safe_block, "unexpected safe_block") - assert_true(result.input_count == 0, "expected zero inputs") - - log.step(scenario, 4, 5, "verify manifest-backed checkpoint was written") - local current = checkpoint.load(checkpoint_dir) - assert_true(current, "checkpoint current.json missing after advance") - assert_true(current.safe_block == target_safe_block, "checkpoint safe_block mismatch") - - log.step(scenario, 5, 5, "verify snapshot directory exists under checkpoint") - assert_true(path_is_dir(current.snapshot_dir), "checkpoint snapshot dir missing") - log.info("wrote checkpoint safe_block=" .. tostring(current.safe_block)) - end, -}) - table.insert(scenarios, { name = "cm-inspect-state-query", fn = function() local scenario = "cm-inspect-state-query" - if require_cartesi_machine(scenario) == "skip" then - return "skip" - end - if require_machine_image(scenario) == "skip" then - return "skip" - end - if require_machine_cartesi_binding(scenario) == "skip" then - return "skip" - end + require_cartesi_machine() + require_machine_image() + require_machine_cartesi_binding() log.step(scenario, 1, 4, "create machine_cartesi adapter") local machine = machine_cartesi.new() @@ -248,42 +189,40 @@ table.insert(scenarios, { "set WATCHDOG_E2E_SEQUENCER_URL to a live sequencer base URL to run this scenario" ) end - if require_cartesi_machine(scenario) == "skip" then - return "skip" - end - if require_machine_image(scenario) == "skip" then - return "skip" - end - if require_machine_cartesi_binding(scenario) == "skip" then - return "skip" - end + require_cartesi_machine() + require_machine_image() + require_machine_cartesi_binding() local http_mod = require("watchdog.http") local jsonrpc = require("watchdog.jsonrpc") local sequencer_reader = require("watchdog.sequencer_reader") local json = require("watchdog.json").new() + local main_mod = require("watchdog.main") - local checkpoint_dir = temp_dir("watchdog-e2e-compare") - log.step(scenario, 1, 2, "prepare compare-mode deps (sequencer=" .. sequencer_url .. ")") + local state_dir = temp_dir("watchdog-e2e-compare") + log.step(scenario, 1, 2, "prepare watchdog deps (sequencer=" .. sequencer_url .. ")") log.step(scenario, 2, 2, "run compare runner against live sequencer + CM") local http = http_mod.new() local cfg = { - mode = "compare", sequencer_url = sequencer_url, - checkpoint_dir = checkpoint_dir, + state_dir = state_dir, cm_snapshot_dir = MACHINE_IMAGE, cm_snapshot_safe_block = GENESIS_SAFE_BLOCK, - cm_executable = "cartesi-machine", - cm_work_dir = temp_dir("watchdog-e2e-compare-work"), l1_rpc_url = os.getenv("WATCHDOG_E2E_L1_RPC_URL") or "http://127.0.0.1:8545", input_box_address = os.getenv("WATCHDOG_E2E_INPUTBOX_ADDRESS") or "0x0000000000000000000000000000000000000000", app_address = os.getenv("WATCHDOG_E2E_APP_ADDRESS") or "0x1111111111111111111111111111111111111111", long_block_range_error_codes = { "-32005" }, + retry_attempts = 1, + retry_delay_sec = 0, } + assert_true(main_mod.run_init(cfg, { + machine = machine_cartesi.new(), + }), "watchdog init failed") + local step_no = 0 local result, err = runner.run_once(cfg, { http = http, @@ -309,47 +248,34 @@ table.insert(scenarios, { name = "machine-cartesi-store-reload-advance", fn = function() local scenario = "machine-cartesi-store-reload-advance" - if require_cartesi_machine(scenario) == "skip" then - return "skip" - end - if require_machine_image(scenario) == "skip" then - return "skip" - end - if require_machine_cartesi_binding(scenario) == "skip" then - return "skip" - end + require_cartesi_machine() + require_machine_image() + require_machine_cartesi_binding() local checkpoint_dir = temp_dir("watchdog-e2e-store-reload") - log.step(scenario, 1, 4, "advance from genesis to safe_block=1 and store checkpoint") - local first, first_err = runner.advance_checkpoint_once(advance_cfg(checkpoint_dir, 1), { - machine = machine_cartesi.new(), - log_step = make_step_logger(scenario .. "/first", 8), - fetch_inputs = function(from_block, to_block) - assert_true(from_block == 1, "expected first from_block=1") - assert_true(to_block == 1, "expected first to_block=1") - return {} - end, - }) - assert_true(first, "first advance failed: " .. tostring(first_err)) - assert_true(first.safe_block == 1, "first safe_block mismatch") - log.step(scenario, 2, 4, "reload checkpoint and advance to safe_block=2") - local second, second_err = runner.advance_checkpoint_once(advance_cfg(checkpoint_dir, 2), { - machine = machine_cartesi.new(), - log_step = make_step_logger(scenario .. "/second", 8), - fetch_inputs = function(from_block, to_block) - assert_true(from_block == 2, "expected second from_block=2") - assert_true(to_block == 2, "expected second to_block=2") - return {} - end, - }) - assert_true(second, "second advance failed: " .. tostring(second_err)) - assert_true(second.safe_block == 2, "second safe_block mismatch") + -- Advance the in-process CM one block from `source_dir` and store a + -- checkpoint at `block` via checkpoint.write (the production store path). + local function advance_and_store(source_dir, prev_block, block) + local machine = machine_cartesi.new() + local instance = assert(machine:load(source_dir, prev_block), "load failed") + assert(machine:advance(instance, {}, { from_block = prev_block + 1, to_block = block })) + return checkpoint.write(checkpoint_dir, block, function(snapshot_dir) + return machine:dump(instance, snapshot_dir, block) + end, { created_at = "2026-01-01T00:00:00Z" }) + end + + log.step(scenario, 1, 4, "advance genesis -> block 1 and store a checkpoint") + assert_true(advance_and_store(MACHINE_IMAGE, GENESIS_SAFE_BLOCK, 1), "first store failed") + + log.step(scenario, 2, 4, "reload the stored block-1 snapshot and advance -> block 2") + local reloaded = checkpoint.load(checkpoint_dir) + assert_true(reloaded and reloaded.safe_block == 1, "checkpoint at block 1 missing") + assert_true(advance_and_store(reloaded.snapshot_dir, 1, 2), "reload + store failed") - log.step(scenario, 3, 4, "load current checkpoint metadata") + log.step(scenario, 3, 4, "load current checkpoint metadata (block 1 should be GC'd)") local current = checkpoint.load(checkpoint_dir) - assert_true(current, "checkpoint missing after second advance") - assert_true(current.safe_block == 2, "current safe_block mismatch") + assert_true(current and current.safe_block == 2, "current safe_block mismatch") log.step(scenario, 4, 4, "verify snapshot directory exists") assert_true(path_is_dir(current.snapshot_dir), "stored snapshot directory missing") diff --git a/watchdog/tests/run.lua b/watchdog/tests/run.lua index 4b18e7a..d949dcb 100644 --- a/watchdog/tests/run.lua +++ b/watchdog/tests/run.lua @@ -9,9 +9,11 @@ local compare = require("watchdog.compare") local config = require("watchdog.config") local jsonrpc = require("watchdog.jsonrpc") local l1_reader = require("watchdog.l1_reader") +local main_mod = require("watchdog.main") local retry = require("watchdog.retry") local runner = require("watchdog.runner") local sequencer_reader = require("watchdog.sequencer_reader") +local state_mod = require("watchdog.state") local tests = {} @@ -142,6 +144,62 @@ test("shared partition vector matches l1_reader bisect plan", function() end end) +test("l1_reader streams successful partitions in L1 order", function() + local calls = {} + local chunks = {} + local rpc = {} + function rpc.get_logs(_self, filter) + table.insert(calls, { filter.from_block, filter.to_block }) + if filter.from_block == 1 and filter.to_block == 4 then + return nil, "-32005: range too large" + end + if filter.from_block == 1 and filter.to_block == 2 then + return { + { blockNumber = "0x2", transactionIndex = "0x0", logIndex = "0x2" }, + { blockNumber = "0x1", transactionIndex = "0x0", logIndex = "0x1" }, + } + end + if filter.from_block == 3 and filter.to_block == 4 then + return { + { blockNumber = "0x4", transactionIndex = "0x0", logIndex = "0x0" }, + } + end + error("unexpected range") + end + + local count, err = l1_reader.for_each_log_chunk_partitioned(rpc, { + start_block = 1, + end_block = 4, + input_box_address = "0xinputbox", + app_address = "0x1111111111111111111111111111111111111111", + long_block_range_error_codes = { "-32005" }, + }, function(logs, range) + table.insert(chunks, { + from_block = range.from_block, + to_block = range.to_block, + first_block = logs[1] and logs[1].blockNumber, + count = #logs, + }) + return true + end) + + assert(count, err) + assert_eq(count, 3) + assert_eq(#calls, 3) + assert_eq(calls[1][1], 1) + assert_eq(calls[1][2], 4) + assert_eq(calls[2][1], 1) + assert_eq(calls[2][2], 2) + assert_eq(calls[3][1], 3) + assert_eq(calls[3][2], 4) + assert_eq(#chunks, 2) + assert_eq(chunks[1].from_block, 1) + assert_eq(chunks[1].to_block, 2) + assert_eq(chunks[1].first_block, "0x1") + assert_eq(chunks[2].from_block, 3) + assert_eq(chunks[2].to_block, 4) +end) + test("l1_reader ensure_rpc_head_at_least accepts head at target", function() local head, err = l1_reader.ensure_rpc_head_at_least({ get_block_number_by_tag = function(_self, tag) @@ -222,11 +280,11 @@ end) test("config loads snapshot directory safe block and optional topic", function() local env = { - WATCHDOG_L1_RPC_URL = "http://rpc", + WATCHDOG_SEQUENCER_URL = "http://seq", WATCHDOG_INPUTBOX_ADDRESS = "0x9999999999999999999999999999999999999999", WATCHDOG_APP_ADDRESS = "0x1111111111111111111111111111111111111111", WATCHDOG_INPUT_ADDED_TOPIC = "0xtopic", - WATCHDOG_CHECKPOINT_DIR = "/tmp/checkpoints", + WATCHDOG_STATE_DIR = "/tmp/watchdog-state", WATCHDOG_CM_SNAPSHOT_DIR = "/tmp/snapshot", WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK = "42", } @@ -234,26 +292,25 @@ test("config loads snapshot directory safe block and optional topic", function() local cfg = config.load(env) assert_eq(cfg.input_added_topic, "0xtopic") + assert_eq(cfg.state_dir, "/tmp/watchdog-state") assert_eq(cfg.cm_snapshot_dir, "/tmp/snapshot") assert_eq(cfg.cm_snapshot_safe_block, 42) - assert_eq(cfg.mode, "advance") + assert_eq(cfg.l1_rpc_url, nil) end) -test("config rejects unknown mode", function() +test("config requires a sequencer URL", function() local ok, err = pcall(function() config.load({ - WATCHDOG_MODE = "bad", - WATCHDOG_L1_RPC_URL = "http://rpc", WATCHDOG_INPUTBOX_ADDRESS = "0x9999999999999999999999999999999999999999", WATCHDOG_APP_ADDRESS = "0x1111111111111111111111111111111111111111", - WATCHDOG_CHECKPOINT_DIR = "/tmp/checkpoints", + WATCHDOG_STATE_DIR = "/tmp/watchdog-state", }) end) assert_eq(ok, false) - assert(tostring(err):find("WATCHDOG_MODE", 1, true) ~= nil, "mode error is explicit") + assert(tostring(err):find("WATCHDOG_SEQUENCER_URL", 1, true) ~= nil, "sequencer URL is required") end) -test("checkpoint writes manifest-backed current pointer", function() +test("checkpoint writes manifest-backed head pointer", function() local dir = os.tmpname() os.remove(dir) os.execute(string.format('mkdir -p "%s"', dir)) @@ -276,6 +333,29 @@ test("checkpoint writes manifest-backed current pointer", function() assert(loaded.manifest_json:find('"safe_block":12', 1, true) ~= nil, "manifest has safe block") end) +test("checkpoint load rejects missing head pointer", function() + local dir = os.tmpname() + os.remove(dir) + os.execute(string.format('mkdir -p "%s"', dir)) + + local loaded, err = checkpoint.load(dir) + assert_eq(loaded, nil) + assert_eq(err, "missing head.json") +end) + +test("checkpoint rejects head pointer outside checkpoint namespace", function() + local dir = os.tmpname() + os.remove(dir) + os.execute(string.format('mkdir -p "%s"', dir)) + local file = assert(io.open(dir .. "/head.json", "wb"), "head pointer opened") + file:write('{"checkpoint":"../outside"}\n') + file:close() + + local loaded, err = checkpoint.load(dir) + assert_eq(loaded, nil) + assert_eq(err, "invalid checkpoint pointer") +end) + test("checkpoint rejects manifest without safe block", function() local safe_block, err = checkpoint.safe_block_from_manifest("{}") assert_eq(safe_block, nil) @@ -309,15 +389,87 @@ test("checkpoint prepare clears stale snapshot dir before write", function() assert_eq(io.open(stale_snapshot .. "/garbage", "rb"), nil) end) +test("checkpoint refuses same-block rewrite before clearing selected snapshot", function() + local dir = os.tmpname() + os.remove(dir) + + local written, err = checkpoint.write(dir, 12, function(snapshot_dir) + os.execute(string.format('mkdir -p "%s"', snapshot_dir)) + local file = io.open(snapshot_dir .. "/marker", "wb") + assert(file ~= nil, "marker file opened") + file:write("original") + file:close() + return true + end) + assert(written, err) + + local second, second_err = checkpoint.write(dir, 12, function(_snapshot_dir) + error("same-block rewrite must fail before snapshot_writer") + end) + assert_eq(second, nil) + assert(tostring(second_err):find("refusing to rewrite selected checkpoint", 1, true) ~= nil, tostring(second_err)) + + local marker = io.open(dir .. "/checkpoints/00000000000000000012/snapshot/marker", "rb") + assert(marker ~= nil, "selected checkpoint snapshot remains intact") + assert_eq(marker:read("*a"), "original") + marker:close() +end) + +test("checkpoint write keeps only the current checkpoint (prunes predecessor)", function() + local dir = os.tmpname() + os.remove(dir) + os.execute(string.format('mkdir -p "%s"', dir)) + + -- A non-checkpoint sentinel must never be touched by GC: head.json never + -- points at it. + local sentinel = dir .. "/genesis-image" + os.execute(string.format('mkdir -p "%s"', sentinel)) + + local function write_block(safe_block) + local written, err = checkpoint.write(dir, safe_block, function(snapshot_dir) + os.execute(string.format('mkdir -p "%s"', snapshot_dir)) + local file = assert(io.open(snapshot_dir .. "/marker", "wb"), "marker opened") + file:write("snapshot") + file:close() + return true + end) + assert(written, err) + end + + local function dir_exists(path) + local ok, _, code = os.rename(path, path) + return ok or code == 13 + end + + write_block(1) + write_block(2) + write_block(3) + + -- Only the latest checkpoint survives; the two predecessors are reclaimed. + assert(dir_exists(dir .. "/checkpoints/00000000000000000003"), "current checkpoint kept") + assert_eq(dir_exists(dir .. "/checkpoints/00000000000000000002"), false) + assert_eq(dir_exists(dir .. "/checkpoints/00000000000000000001"), false) + + -- head.json still resolves to the latest, and GC never touched the sentinel. + local loaded, load_err = checkpoint.load(dir) + assert(loaded, load_err) + assert_eq(loaded.safe_block, 3) + assert(dir_exists(sentinel), "non-checkpoint dir must be untouched") +end) + local function fake_cfg() return { - checkpoint_dir = "/tmp/watchdog-test", + state_dir = "/tmp/watchdog-test", + sequencer_url = "http://sequencer", + l1_rpc_url = "http://rpc", cm_snapshot_dir = "/tmp/genesis-snapshot", cm_snapshot_safe_block = 0, input_box_address = "0xinputbox", app_address = "0x1111111111111111111111111111111111111111", input_added_topic = "0xtopic", long_block_range_error_codes = l1_reader.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES, + retry_attempts = 1, + retry_delay_sec = 0, } end @@ -325,6 +477,7 @@ local function fake_machine(inspect_state) local machine = { loaded_path = nil, fed_inputs = nil, + advance_calls = {}, } function machine:load(path, reference_block) self.loaded_path = path @@ -332,6 +485,11 @@ local function fake_machine(inspect_state) end function machine:advance(_instance, inputs, range) self.fed_inputs = inputs + table.insert(self.advance_calls, { + from_block = range.from_block, + to_block = range.to_block, + input_count = #inputs, + }) _instance.reference_block = range.to_block return true end @@ -356,6 +514,75 @@ local function fake_machine(inspect_state) return machine end +test("init stores bootstrap snapshot as watchdog head", function() + local dir = os.tmpname() + os.remove(dir) + + local cfg = fake_cfg() + cfg.state_dir = dir + cfg.cm_snapshot_safe_block = 5 + + local machine = fake_machine("{}") + local result, err = main_mod.run_init(cfg, { + machine = machine, + }) + assert(result, err) + assert_eq(result.safe_block, 5) + assert_eq(machine.loaded_path, "/tmp/genesis-snapshot") + + local loaded, load_err = checkpoint.load(dir) + assert(loaded, load_err) + assert_eq(loaded.safe_block, 5) + assert_eq(loaded.snapshot_dir, dir .. "/checkpoints/00000000000000000005/snapshot") + + local persisted, cfg_err = state_mod.read_json(dir, "config.json", require("watchdog.json").new()) + assert(persisted, cfg_err) + assert_eq(persisted.sequencer_url, "http://sequencer") + assert_eq(persisted.l1_rpc_url, nil) + + local tick_cfg = main_mod.load_tick_config({ + WATCHDOG_STATE_DIR = dir, + WATCHDOG_L1_RPC_URL = "http://tick-rpc", + }) + assert_eq(tick_cfg.state_dir, dir) + assert_eq(tick_cfg.sequencer_url, "http://sequencer") + assert_eq(tick_cfg.l1_rpc_url, "http://tick-rpc") +end) + +test("tick config requires current RPC URL outside persisted state", function() + local dir = os.tmpname() + os.remove(dir) + + local cfg = fake_cfg() + cfg.state_dir = dir + + local result, err = main_mod.run_init(cfg, { machine = fake_machine("{}") }) + assert(result, err) + + local ok, load_err = pcall(function() + main_mod.load_tick_config({ + WATCHDOG_STATE_DIR = dir, + }) + end) + assert_eq(ok, false) + assert(tostring(load_err):find("WATCHDOG_L1_RPC_URL", 1, true) ~= nil, tostring(load_err)) +end) + +test("init refuses an already initialized state directory", function() + local dir = os.tmpname() + os.remove(dir) + + local cfg = fake_cfg() + cfg.state_dir = dir + + local first, first_err = main_mod.run_init(cfg, { machine = fake_machine("{}") }) + assert(first, first_err) + + local second, second_err = main_mod.run_init(cfg, { machine = fake_machine("{}") }) + assert_eq(second, nil) + assert(tostring(second_err):find("already initialized", 1, true) ~= nil, tostring(second_err)) +end) + test("runner happy path replays inputs and writes checkpoint", function() local checkpoint_writes = {} local checkpoint_mod = { @@ -409,6 +636,121 @@ test("runner happy path replays inputs and writes checkpoint", function() assert_eq(checkpoint_writes[1].safe_block, 12) end) +test("runner advances CM as streamed input chunks arrive", function() + local checkpoint_writes = {} + local checkpoint_mod = { + load = function(_dir) + return { + snapshot_dir = "/tmp/checkpoints/0001/snapshot", + safe_block = 10, + } + end, + write = function(_dir, safe_block, snapshot_writer, _manifest) + local ok, err = snapshot_writer("/tmp/new-snapshot") + assert(ok, err) + table.insert(checkpoint_writes, safe_block) + return true + end, + } + local machine = fake_machine('{"ok":true}') + local result, err = runner.run_once(fake_cfg(), { + checkpoint = checkpoint_mod, + sequencer = { + get_finalized_inclusion_block = function() + return { inclusion_block = 12, l2_tx_index = 0 } + end, + get_finalized_state = function() + return { + inclusion_block = 12, + l2_tx_index = 0, + state = '{"ok":true}', + } + end, + }, + for_each_input_chunk = function(from_block, to_block, on_chunk) + assert_eq(from_block, 11) + assert_eq(to_block, 12) + local ok, chunk_err = on_chunk({ { raw_input = "a" } }, { + from_block = 11, + to_block = 11, + }) + assert(ok, chunk_err) + ok, chunk_err = on_chunk({ { raw_input = "b" }, { raw_input = "c" } }, { + from_block = 12, + to_block = 12, + }) + assert(ok, chunk_err) + return 3 + end, + machine = machine, + }) + + assert(result, err) + assert_eq(result.input_count, 3) + assert_eq(#machine.advance_calls, 2) + assert_eq(machine.advance_calls[1].from_block, 11) + assert_eq(machine.advance_calls[1].input_count, 1) + assert_eq(machine.advance_calls[2].from_block, 12) + assert_eq(machine.advance_calls[2].input_count, 2) + assert_eq(#checkpoint_writes, 1) + assert_eq(checkpoint_writes[1], 12) +end) + +test("runner advances CM over empty streamed partitions", function() + local machine = fake_machine('{"ok":true}') + local result, err = runner.run_once(fake_cfg(), { + checkpoint = { + load = function(_dir) + return { + snapshot_dir = "/tmp/checkpoints/0001/snapshot", + safe_block = 10, + } + end, + write = function(_dir, safe_block, snapshot_writer, _manifest) + local ok, write_err = snapshot_writer("/tmp/new-snapshot") + assert(ok, write_err) + assert_eq(safe_block, 12) + return true + end, + }, + sequencer = { + get_finalized_inclusion_block = function() + return { inclusion_block = 12, l2_tx_index = 0 } + end, + get_finalized_state = function() + return { + inclusion_block = 12, + l2_tx_index = 0, + state = '{"ok":true}', + } + end, + }, + for_each_input_chunk = function(_from_block, _to_block, on_chunk) + local ok, chunk_err = on_chunk({}, { + from_block = 11, + to_block = 11, + }) + assert(ok, chunk_err) + ok, chunk_err = on_chunk({ { raw_input = "a" } }, { + from_block = 12, + to_block = 12, + }) + assert(ok, chunk_err) + return 1 + end, + machine = machine, + }) + + assert(result, err) + assert_eq(result.input_count, 1) + assert_eq(#machine.advance_calls, 2) + assert_eq(machine.advance_calls[1].from_block, 11) + assert_eq(machine.advance_calls[1].to_block, 11) + assert_eq(machine.advance_calls[1].input_count, 0) + assert_eq(machine.advance_calls[2].from_block, 12) + assert_eq(machine.advance_calls[2].input_count, 1) +end) + test("runner returns state mismatch payload", function() local result, err = runner.run_once(fake_cfg(), { checkpoint = { @@ -441,6 +783,27 @@ test("runner returns state mismatch payload", function() assert_eq(err.kind, "state_mismatch") end) +test("runner refuses missing or corrupt watchdog head", function() + local ok, err = pcall(function() + return runner.run_once(fake_cfg(), { + checkpoint = { + load = function(_dir) + return nil, "invalid checkpoint pointer" + end, + }, + sequencer = { + get_finalized_inclusion_block = function() + error("sequencer must not be queried after corrupt checkpoint") + end, + }, + machine = fake_machine("{}"), + }) + end) + + assert_eq(ok, false) + assert(tostring(err):find("failed to load watchdog head", 1, true) ~= nil, tostring(err)) +end) + test("runner returns transient error when L1 RPC head lags target block", function() local result, err = runner.run_once(fake_cfg(), { checkpoint = { @@ -629,39 +992,6 @@ test("sequencer client rejects invalid inclusion_block JSON", function() assert_eq(err, "invalid finalized inclusion_block response JSON") end) -test("advance runner fetches inputs and saves checkpoint without sequencer", function() - local checkpoint_writes = {} - local machine = fake_machine("unused") - local result, err = runner.advance_checkpoint_once(fake_cfg(), { - checkpoint = { - load = function(_dir) - return { snapshot_dir = "/tmp/snapshot", safe_block = 7 } - end, - write = function(dir, safe_block, snapshot_writer, manifest) - local ok, write_err = snapshot_writer("/tmp/advanced-snapshot") - assert(ok, write_err) - table.insert(checkpoint_writes, { dir = dir, safe_block = safe_block, manifest = manifest }) - return true - end, - }, - safe_block = function() - return 9 - end, - fetch_inputs = function(from_block, to_block) - assert_eq(from_block, 8) - assert_eq(to_block, 9) - return { { raw_input = "one" }, { raw_input = "two" } } - end, - machine = machine, - }) - - assert(result, err) - assert_eq(result.safe_block, 9) - assert_eq(result.input_count, 2) - assert_eq(machine.saved_snapshot_dir, "/tmp/advanced-snapshot") - assert_eq(#checkpoint_writes, 1) -end) - test("retry succeeds after transient failures", function() local attempts = 0 local sleeps = 0 diff --git a/watchdog/third_party/lua-curl/LICENSE b/watchdog/third_party/lua-curl/LICENSE new file mode 100644 index 0000000..3850127 --- /dev/null +++ b/watchdog/third_party/lua-curl/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2021 Alexey Melnichuk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/watchdog/third_party/lua-curl/UPSTREAM b/watchdog/third_party/lua-curl/UPSTREAM index 84782fe..373e0e4 100644 --- a/watchdog/third_party/lua-curl/UPSTREAM +++ b/watchdog/third_party/lua-curl/UPSTREAM @@ -1,12 +1,15 @@ Lua-cURLv3 (lcurl native binding; libcurl must be installed on the host). -URL: https://github.com/Lua-cURL/Lua-cURLv3 -Commit: 9f8b6dba8b5ef1b26309a571ae75cda4034279e5 -License: MIT (see upstream LICENSE) +URL: https://github.com/Lua-cURL/Lua-cURLv3 +Commit: 9f8b6dba8b5ef1b26309a571ae75cda4034279e5 +License: MIT (see LICENSE in this directory) -Sources are not vendored in git. `scripts/watchdog-lua-deps.sh` downloads this -pinned tarball at build time into `.deps/lua-curl-src//` and compiles -`lcurl.so` into `.deps/lua/`. Pin is also recorded in `release/versions.env` -as `LUA_CURL_UPSTREAM_SHA`. +Vendored in-tree: the curated C subset is committed under `src/` (the `src/*.c` ++ `src/*.h` that make up the `lcurl` C module). We do NOT vendor the optional +`cURL/*.lua` helper layer, the upstream Makefile/build glue, tests, docs, or CI +config. `scripts/watchdog-lua-deps.sh` compiles `src/*.c` directly into +`.deps/lua/lcurl.so` -- there is no build-time download and no pin to verify, +because the compiled bytes are exactly this in-tree source. -We build only the lcurl C module — not the optional `cURL/*.lua` helper layer. +Updating: replace `src/` from a newer upstream commit, refresh LICENSE, and +update the Commit line above. diff --git a/watchdog/third_party/lua-curl/src/l52util.c b/watchdog/third_party/lua-curl/src/l52util.c new file mode 100644 index 0000000..6373687 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/l52util.c @@ -0,0 +1,178 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "l52util.h" + +#include +#include /* for memset */ +#include + +#if LUA_VERSION_NUM >= 502 + +int luaL_typerror (lua_State *L, int narg, const char *tname) { + const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, + luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); +} + +#ifndef luaL_register + +void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l){ + if(libname) lua_newtable(L); + luaL_setfuncs(L, l, 0); +} + +#endif + +#else + +void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup){ + luaL_checkstack(L, nup, "too many upvalues"); + for (; l->name != NULL; l++) { /* fill the table with given functions */ + int i; + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -nup); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + lua_setfield(L, -(nup + 2), l->name); + } + lua_pop(L, nup); /* remove upvalues */ +} + +void lua_rawgetp(lua_State *L, int index, const void *p){ + index = lua_absindex(L, index); + lua_pushlightuserdata(L, (void *)p); + lua_rawget(L, index); +} + +void lua_rawsetp (lua_State *L, int index, const void *p){ + index = lua_absindex(L, index); + lua_pushlightuserdata(L, (void *)p); + lua_insert(L, -2); + lua_rawset(L, index); +} + +#endif + +int lutil_newmetatablep (lua_State *L, const void *p) { + lua_rawgetp(L, LUA_REGISTRYINDEX, p); + if (!lua_isnil(L, -1)) /* name already in use? */ + return 0; /* leave previous value on top, but return 0 */ + lua_pop(L, 1); + + lua_newtable(L); /* create metatable */ + lua_pushvalue(L, -1); /* duplicate metatable to set*/ + + lua_pushliteral (L, "__type"); + lua_pushstring(L, p); // push meta name + lua_settable (L, -3); // set meta name + + lua_rawsetp(L, LUA_REGISTRYINDEX, p); + + return 1; +} + +void lutil_getmetatablep (lua_State *L, const void *p) { + lua_rawgetp(L, LUA_REGISTRYINDEX, p); +} + +void lutil_setmetatablep (lua_State *L, const void *p) { + lutil_getmetatablep(L, p); + assert(lua_istable(L,-1)); + lua_setmetatable (L, -2); +} + +int lutil_isudatap (lua_State *L, int ud, const void *p) { + if (lua_isuserdata(L, ud)){ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + int res; + lutil_getmetatablep(L,p); /* get correct metatable */ + res = lua_rawequal(L, -1, -2); /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return res; + } + } + return 0; +} + +void *lutil_checkudatap (lua_State *L, int ud, const void *p) { + void *up = lua_touserdata(L, ud); + if (up != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + lutil_getmetatablep(L,p); /* get correct metatable */ + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return up; + } + } + } + luaL_typerror(L, ud, p); /* else error */ + return NULL; /* to avoid warnings */ +} + +int lutil_createmetap (lua_State *L, const void *p, const luaL_Reg *methods, int nup) { + if (!lutil_newmetatablep(L, p)){ + lua_insert(L, -1 - nup); /* move mt prior upvalues */ + return 0; + } + + lua_insert(L, -1 - nup); /* move mt prior upvalues */ + luaL_setfuncs (L, methods, nup); /* define methods */ + lua_pushliteral (L, "__index"); /* define metamethods */ + lua_pushvalue (L, -2); + lua_settable (L, -3); + return 1; +} + +void *lutil_newudatap_impl(lua_State *L, size_t size, const void *p){ + void *obj = lua_newuserdata (L, size); + memset(obj, 0, size); + lutil_setmetatablep(L, p); + return obj; +} + +void lutil_pushint64(lua_State *L, int64_t v){ + if(sizeof(lua_Integer) >= sizeof(int64_t)){ + lua_pushinteger(L, (lua_Integer)v); + return; + } + lua_pushnumber(L, (lua_Number)v); +} + +void lutil_pushuint(lua_State *L, unsigned int v){ +#if LUA_VERSION_NUM >= 503 + lua_pushinteger(L, (lua_Integer)v); +#else + lua_pushnumber(L, (lua_Number)v); +#endif +} + +int64_t lutil_checkint64(lua_State *L, int idx){ + if(sizeof(lua_Integer) >= sizeof(int64_t)) + return luaL_checkinteger(L, idx); + return (int64_t)luaL_checknumber(L, idx); +} + +int64_t lutil_optint64(lua_State *L, int idx, int64_t v){ + if(sizeof(lua_Integer) >= sizeof(int64_t)) + return luaL_optinteger(L, idx, v); + return (int64_t)luaL_optnumber(L, idx, v); +} + +void lutil_pushnvalues(lua_State *L, int n){ + for(;n;--n) lua_pushvalue(L, -n); +} + +int lutil_is_null(lua_State *L, int i){ + return lua_islightuserdata(L, i) && 0 == lua_touserdata(L, i); +} + +void lutil_push_null(lua_State *L){ + lua_pushlightuserdata(L, (void*)0); +} diff --git a/watchdog/third_party/lua-curl/src/l52util.h b/watchdog/third_party/lua-curl/src/l52util.h new file mode 100644 index 0000000..97348a0 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/l52util.h @@ -0,0 +1,97 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _L52UTIL_H_ +#define _L52UTIL_H_ + +#include "lua.h" +#include "lauxlib.h" +#include + +#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ + +#ifndef luaL_checkint +#define luaL_checkint luaL_checkinteger +#endif + +#ifndef luaL_checklong +#define luaL_checklong luaL_checkinteger +#endif + +#ifndef luaL_optint +#define luaL_optint luaL_optinteger +#endif + +#ifndef luaL_optlong +#define luaL_optlong luaL_optinteger +#endif + +#endif + +#if LUA_VERSION_NUM >= 502 /* Lua 5.2 */ + +/* lua_rawgetp */ +/* lua_rawsetp */ +/* luaL_setfuncs */ +/* lua_absindex */ + +#ifndef lua_objlen +#define lua_objlen lua_rawlen +#endif + +int luaL_typerror (lua_State *L, int narg, const char *tname); + +#ifndef luaL_register +void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l); +#endif + +#ifndef lua_equal +#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#endif + +#else /* Lua 5.1 */ + +/* functions from lua 5.2 */ + +# define lua_absindex(L, i) (((i)>0)?(i):((i)<=LUA_REGISTRYINDEX?(i):(lua_gettop(L)+(i)+1))) +# define lua_rawlen lua_objlen + +void lua_rawgetp (lua_State *L, int index, const void *p); +void lua_rawsetp (lua_State *L, int index, const void *p); +void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); + +#endif + +int lutil_newmetatablep (lua_State *L, const void *p); +void lutil_getmetatablep (lua_State *L, const void *p); +void lutil_setmetatablep (lua_State *L, const void *p); + +#define lutil_newudatap(L, TTYPE, TNAME) (TTYPE *)lutil_newudatap_impl(L, sizeof(TTYPE), TNAME) +int lutil_isudatap (lua_State *L, int ud, const void *p); +void *lutil_checkudatap (lua_State *L, int ud, const void *p); +int lutil_createmetap (lua_State *L, const void *p, const luaL_Reg *methods, int nup); + +void *lutil_newudatap_impl (lua_State *L, size_t size, const void *p); + +void lutil_pushuint(lua_State *L, unsigned int v); + +void lutil_pushint64(lua_State *L, int64_t v); + +int64_t lutil_checkint64(lua_State *L, int idx); + +int64_t lutil_optint64(lua_State *L, int idx, int64_t v); + +void lutil_pushnvalues(lua_State *L, int n); + +int lutil_is_null(lua_State *L, int i); + +void lutil_push_null(lua_State *L); + +#endif diff --git a/watchdog/third_party/lua-curl/src/lceasy.c b/watchdog/third_party/lua-curl/src/lceasy.c new file mode 100644 index 0000000..ad48022 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lceasy.c @@ -0,0 +1,2469 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lceasy.h" +#include "lcerror.h" +#include "lcutils.h" +#include "lchttppost.h" +#include "lcshare.h" +#include "lcmulti.h" +#include "lcmime.h" +#include "lcurlapi.h" +#include + +static const char *LCURL_ERROR_TAG = "LCURL_ERROR_TAG"; + +#define LCURL_EASY_NAME LCURL_PREFIX" Easy" +static const char *LCURL_EASY = LCURL_EASY_NAME; + +#if LCURL_CURL_VER_GE(7,21,5) +# define LCURL_E_UNKNOWN_OPTION CURLE_UNKNOWN_OPTION +#else +# define LCURL_E_UNKNOWN_OPTION CURLE_UNKNOWN_TELNET_OPTION +#endif + +/* Before call curl_XXX function which can call any callback + * need set Current Lua thread pointer in easy/multi contexts. + * But it also possible that we already in callback call. + * E.g. `curl_easy_pause` function may be called from write callback. + * and it even may be called in different thread. + * ```Lua + * multi:add_handle(easy) + * easy:setopt_writefunction(function(...) + * coroutine.wrap(function() multi:add_handle(easy2) end)() + * end) + * ``` + * So we have to restore previews Lua state in callback contexts. + * But if previews Lua state is NULL then we can just do not set it back. + * But set it to NULL make easier to debug code. + */ +void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi){ + if(p->multi && assign_multi){ + lcurl__multi_assign_lua(L, p->multi, value, 1); + } + else{ + p->L = value; + if(p->post){ + p->post->L = value; + } +#if LCURL_CURL_VER_GE(7,56,0) + if(p->mime){ + lcurl_mime_set_lua(L, p->mime, value); + } +#endif + } +} + +//{ + +int lcurl_easy_create(lua_State *L, int error_mode){ + lcurl_easy_t *p; + int i; + + lua_settop(L, 1); /* options */ + + p = lutil_newudatap(L, lcurl_easy_t, LCURL_EASY); + + p->curl = curl_easy_init(); + + p->err_mode = error_mode; + if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); + + p->magic = LCURL_EASY_MAGIC; + p->L = NULL; + p->post = NULL; + p->multi = NULL; +#if LCURL_CURL_VER_GE(7,56,0) + p->mime = NULL; +#endif + p->storage = lcurl_storage_init(L); + p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; + p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + p->hd.cb_ref = p->hd.ud_ref = LUA_NOREF; + p->pr.cb_ref = p->pr.ud_ref = LUA_NOREF; + p->seek.cb_ref = p->seek.ud_ref = LUA_NOREF; + p->debug.cb_ref = p->debug.ud_ref = LUA_NOREF; + p->match.cb_ref = p->match.ud_ref = LUA_NOREF; + p->chunk_bgn.cb_ref = p->chunk_bgn.ud_ref = LUA_NOREF; + p->chunk_end.cb_ref = p->chunk_end.ud_ref = LUA_NOREF; +#if LCURL_CURL_VER_GE(7,19,6) + p->ssh_key.cb_ref = p->ssh_key.ud_ref = LUA_NOREF; +#endif +#if LCURL_CURL_VER_GE(7,64,0) + p->trailer.cb_ref = p->trailer.ud_ref = LUA_NOREF; +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + p->hstsread.cb_ref = p->hstsread.ud_ref = LUA_NOREF; + p->hstswrite.cb_ref = p->hstswrite.ud_ref = LUA_NOREF; +#endif + p->rbuffer.ref = LUA_NOREF; + for(i = 0; i < LCURL_LIST_COUNT; ++i){ + p->lists[i] = LUA_NOREF; + } + + if(lua_type(L, 1) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 1, 2, 1, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); + if(ret) return ret; + assert(lua_gettop(L) == 2); + } + + return 1; +} + +lcurl_easy_t *lcurl_geteasy_at(lua_State *L, int i){ + lcurl_easy_t *p = (lcurl_easy_t *)lutil_checkudatap (L, i, LCURL_EASY); + luaL_argcheck (L, p != NULL, 1, LCURL_EASY_NAME" object expected"); + return p; +} + +static int lcurl_easy_to_s(lua_State *L){ + lcurl_easy_t *p = (lcurl_easy_t *)lutil_checkudatap (L, 1, LCURL_EASY); + lua_pushfstring(L, LCURL_EASY_NAME" (%p)", (void*)p); + return 1; +} + +static int lcurl_easy_cleanup_storage(lua_State *L, lcurl_easy_t *p){ + int i; + + if(p->storage != LUA_NOREF){ + p->storage = lcurl_storage_free(L, p->storage); + } + + p->post = NULL; +#if LCURL_CURL_VER_GE(7,56,0) + p->mime = NULL; +#endif + + luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->match.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->match.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.ud_ref); +#if LCURL_CURL_VER_GE(7,19,6) + luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.ud_ref); +#endif +#if LCURL_CURL_VER_GE(7,64,0) + luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.ud_ref); +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.ud_ref); +#endif + luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); + + p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; + p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + p->hd.cb_ref = p->hd.ud_ref = LUA_NOREF; + p->pr.cb_ref = p->pr.ud_ref = LUA_NOREF; + p->seek.cb_ref = p->seek.ud_ref = LUA_NOREF; + p->debug.cb_ref = p->debug.ud_ref = LUA_NOREF; + p->match.cb_ref = p->match.ud_ref = LUA_NOREF; + p->chunk_bgn.cb_ref = p->chunk_bgn.ud_ref = LUA_NOREF; + p->chunk_end.cb_ref = p->chunk_end.ud_ref = LUA_NOREF; +#if LCURL_CURL_VER_GE(7,19,6) + p->ssh_key.cb_ref = p->ssh_key.ud_ref = LUA_NOREF; +#endif +#if LCURL_CURL_VER_GE(7,64,0) + p->trailer.cb_ref = p->trailer.ud_ref = LUA_NOREF; +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + p->hstsread.cb_ref = p->hstsread.ud_ref = LUA_NOREF; + p->hstswrite.cb_ref = p->hstswrite.ud_ref = LUA_NOREF; +#endif + p->rbuffer.ref = LUA_NOREF; + + for(i = 0; i < LCURL_LIST_COUNT; ++i){ + p->lists[i] = LUA_NOREF; + } +} + +static int lcurl_easy_cleanup(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lua_settop(L, 1); + + if(p->multi){ + LCURL_UNUSED_VAR CURLMcode code = lcurl__multi_remove_handle(L, p->multi, p); + + //! @todo what I can do if I can not remove it??? + } + + if(p->curl){ + lua_State *curL; + + // In my tests when I cleanup some easy handle. + // timerfunction called only for single multi handle. + // Also may be this function may call `close` callback + // for `curl_mimepart` structure. + curL = p->L; lcurl__easy_assign_lua(L, p, L, 1); + curl_easy_cleanup(p->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__easy_assign_lua(L, p, curL, 1); + + p->curl = NULL; + } + + lcurl_easy_cleanup_storage(L, p); + + lua_pushnil(L); + lua_rawset(L, LCURL_USERVALUES); + + return 0; +} + +static int lcurl_easy_perform(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + lua_State *curL; + int top = 1; + lua_settop(L, top); + + assert(p->rbuffer.ref == LUA_NOREF); + + // store reference to current coroutine to callbacks + // User should not call `perform` if handle assign to multi + curL = p->L; lcurl__easy_assign_lua(L, p, L, 0); + code = curl_easy_perform(p->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__easy_assign_lua(L, p, curL, 0); + + if(p->rbuffer.ref != LUA_NOREF){ + luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); + p->rbuffer.ref = LUA_NOREF; + } + + if(code == CURLE_OK){ + lua_settop(L, 1); + return 1; + } + + if((lua_gettop(L) > top)&&(lua_touserdata(L, top + 1) == LCURL_ERROR_TAG)){ + return lua_error(L); + } + + if(code == CURLE_WRITE_ERROR){ + if(lua_gettop(L) > top){ + return lua_gettop(L) - top; + } + } + + if(code == CURLE_ABORTED_BY_CALLBACK){ + if(lua_gettop(L) > top){ + return lua_gettop(L) - top; + } + } + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); +} + +static int lcurl_easy_escape(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + size_t data_size; const char *data = luaL_checklstring(L, 2, &data_size); + const char *ret = curl_easy_escape(p->curl, data, (int)data_size); + if(!ret){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_OUT_OF_MEMORY); + } + lua_pushstring(L, ret); + curl_free((char*)ret); + return 1; +} + +static int lcurl_easy_unescape(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + size_t data_size; const char *data = luaL_checklstring(L, 2, &data_size); + int ret_size; const char *ret = curl_easy_unescape(p->curl, data, (int)data_size, &ret_size); + if(!ret){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_OUT_OF_MEMORY); + } + lua_pushlstring(L, ret, ret_size); + curl_free((char*)ret); + return 1; +} + +static int lcurl_easy_reset(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + curl_easy_reset(p->curl); + lua_settop(L, 1); + + lcurl_easy_cleanup_storage(L, p); + p->storage = lcurl_storage_init(L); + + return 1; +} + +#if LCURL_CURL_VER_GE(7,56,0) + +static int lcurl_easy_mime(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_mime_create(L, p->err_mode); +} + +#endif + +#if LCURL_CURL_VER_GE(7,62,0) + +static int lcurl_easy_upkeep(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_upkeep(p->curl); + if(code == CURLE_OK){ + lua_settop(L, 1); + return 1; + } + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); +} + +#endif + +//{ OPTIONS + +//{ set + +static int lcurl_opt_set_long_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + long val; CURLcode code; + + if(lua_isboolean(L, 2)){ + val = lua_toboolean(L, 2); + if( val + && ( + (opt == CURLOPT_SSL_VERIFYHOST) +#if LCURL_CURL_VER_GE(7,52,0) + || (opt == CURLOPT_PROXY_SSL_VERIFYHOST) +#endif + ) + ){ + val = 2; + } + } + else{ + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number or boolean expected"); + val = luaL_checklong(L, 2); + } + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,59,0) + +static int lcurl_opt_set_off_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + curl_off_t val; CURLcode code; + + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number expected"); + val = lutil_checkint64(L, 2); + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#endif + +static int lcurl_opt_set_string_(lua_State *L, int opt, int store){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; const char *value; + + luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lutil_is_null(L, 2), 2, "string expected"); + + value = lua_tostring(L, 2); + code = curl_easy_setopt(p->curl, opt, value); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + if(store){ + if(value) + lcurl_storage_preserve_iv(L, p->storage, opt, 2); + else + lcurl_storage_remove_i(L, p->storage, opt); + } + + lua_settop(L, 1); + return 1; +} + +static int lcurl_opt_set_slist_(lua_State *L, int opt, int list_no){ + lcurl_easy_t *p = lcurl_geteasy(L); + struct curl_slist *list = lcurl_util_to_slist(L, 2); + CURLcode code; + int ref = p->lists[list_no]; + + luaL_argcheck(L, list || lua_istable(L, 2) || lutil_is_null(L, 2), 2, "array expected"); + + if(ref != LUA_NOREF){ + struct curl_slist *tmp = lcurl_storage_remove_slist(L, p->storage, ref); + curl_slist_free_all(tmp); + p->lists[list_no] = LUA_NOREF; + } + + code = curl_easy_setopt(p->curl, opt, list); + + if(code != CURLE_OK){ + curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + if (list) { + p->lists[list_no] = lcurl_storage_preserve_slist(L, p->storage, list); + } + + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,73,0) + +static int lcurl_opt_set_blob_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; const char *value; size_t len; + struct curl_blob blob; + + luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lutil_is_null(L, 2), 2, "string expected"); + + value = lua_tolstring(L, 2, &len); + + blob.data = (void*)value; + blob.len = len; + blob.flags = CURL_BLOB_COPY; + + code = curl_easy_setopt(p->curl, opt, value); + + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_settop(L, 1); + return 1; +} + +#endif + +#define LCURL_STR_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_string_(L, CURLOPT_##N, (S)); \ +} + +#define LCURL_LST_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_slist_(L, CURLOPT_##N, LCURL_##N##_LIST);\ +} + +#define LCURL_LNG_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_long_(L, CURLOPT_##N);\ +} + +#define LCURL_OFF_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_off_(L, CURLOPT_##N);\ +} + +#define LCURL_BLB_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_blob_(L, CURLOPT_##N); \ +} + +#define OPT_ENTRY(L, N, T, S, D) LCURL_##T##_OPT(N, S) + +#include "lcopteasy.h" + +#undef OPT_ENTRY + +static int lcurl_easy_set_POSTFIELDS(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + size_t len; const char *val = luaL_checklstring(L, 2, &len); + CURLcode code; + if(lua_isnumber(L, 3)){ + size_t n = (size_t)lua_tonumber(L, 3); + luaL_argcheck(L, len <= n, 3, "data length too big"); + len = n; + } + code = curl_easy_setopt(p->curl, CURLOPT_POSTFIELDS, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_POSTFIELDS, 2); + code = curl_easy_setopt(p->curl, CURLOPT_POSTFIELDSIZE, (long)len); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#undef LCURL_STR_OPT +#undef LCURL_LST_OPT +#undef LCURL_LNG_OPT +#undef LCURL_OFF_OPT +#undef LCURL_BLB_OPT + +static size_t lcurl_hpost_read_callback(char *buffer, size_t size, size_t nitems, void *arg); + +static int lcurl_easy_set_HTTPPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_hpost_t *post = lcurl_gethpost_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HTTPPOST, post->post); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_HTTPPOST, 2); + + if(post->stream){ + curl_easy_setopt(p->curl, CURLOPT_READFUNCTION, lcurl_hpost_read_callback); + } + + p->post = post; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_set_SHARE(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_share_t *sh = lcurl_getshare_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SHARE, sh->curl); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_SHARE, 2); + + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,46,0) + +static int lcurl_easy_set_STREAM_DEPENDS_impl(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_easy_t *e = lcurl_geteasy_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, opt, e->curl); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, opt, 2); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_set_STREAM_DEPENDS(lua_State *L){ + return lcurl_easy_set_STREAM_DEPENDS_impl(L, CURLOPT_STREAM_DEPENDS); +} + +static int lcurl_easy_set_STREAM_DEPENDS_E(lua_State *L){ + return lcurl_easy_set_STREAM_DEPENDS_impl(L, CURLOPT_STREAM_DEPENDS_E); +} + +#endif + +#if LCURL_CURL_VER_GE(7,56,0) + +static int lcurl_easy_set_MIMEPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_mime_t *mime = lcurl_getmime_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_MIMEPOST, mime->mime); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_MIMEPOST, 2); + + p->mime = mime; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,63,0) + +static int lcurl_easy_set_CURLU(lua_State *L) { + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_url_t *url = lcurl_geturl_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CURLU, url->url); + if (code != CURLE_OK) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_CURLU, 2); + + lua_settop(L, 1); + return 1; +} + +#endif + +//} + +//{ unset + +static int lcurl_opt_unset_long_(lua_State *L, int opt, long val){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,59,0) + +static int lcurl_opt_unset_off_(lua_State *L, int opt, curl_off_t val){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#endif + +static int lcurl_opt_unset_string_(lua_State *L, int opt, const char *val){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, opt); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_opt_unset_slist_(lua_State *L, int opt, int list_no){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + int ref = p->lists[list_no]; + + code = curl_easy_setopt(p->curl, opt, NULL); + + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + if(ref != LUA_NOREF){ + struct curl_slist *list = lcurl_storage_remove_slist(L, p->storage, ref); + curl_slist_free_all(list); + p->lists[list_no] = LUA_NOREF; + } + + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,73,0) + +static int lcurl_opt_unset_blob_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + + code = curl_easy_setopt(p->curl, opt, 0); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_settop(L, 1); + return 1; +} + +#endif + +#define LCURL_STR_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_string_(L, CURLOPT_##N, (D)); \ +} + +#define LCURL_LST_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_slist_(L, CURLOPT_##N, LCURL_##N##_LIST);\ +} + +#define LCURL_LNG_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_long_(L, CURLOPT_##N, (D));\ +} + +#define LCURL_OFF_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_off_(L, CURLOPT_##N, (D));\ +} + +#define LCURL_BLB_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_blob_(L, CURLOPT_##N);\ +} + +#define OPT_ENTRY(L, N, T, S, D) LCURL_##T##_OPT(N, S, D) + +#include "lcopteasy.h" + +#undef OPT_ENTRY + +#undef LCURL_STR_OPT +#undef LCURL_LST_OPT +#undef LCURL_LNG_OPT +#undef LCURL_OFF_OPT +#undef LCURL_BLB_OPT + +static int lcurl_easy_unset_HTTPPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HTTPPOST, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_get_i(L, p->storage, CURLOPT_HTTPPOST); + if(!lua_isnil(L, -1)){ + lcurl_hpost_t *form = lcurl_gethpost_at(L, -1); + if(form->stream){ + /* with stream we do not set CURLOPT_READDATA but + we also unset it to be sure that there no way to + call default curl reader with our READDATA + */ + curl_easy_setopt(p->curl, CURLOPT_READFUNCTION, NULL); + curl_easy_setopt(p->curl, CURLOPT_READDATA, NULL); + } + lcurl_storage_remove_i(L, p->storage, CURLOPT_HTTPPOST); + } + + p->post = NULL; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_SHARE(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SHARE, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_SHARE); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_WRITEFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_WRITEFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_WRITEDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.ud_ref); + p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_READFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_READFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_READDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); + p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_HEADERFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HEADERFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_HEADERDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.ud_ref); + p->hd.cb_ref = p->hd.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_PROGRESSFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_PROGRESSFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_PROGRESSDATA, NULL); + +#if LCURL_CURL_VER_GE(7,32,0) + curl_easy_setopt(p->curl, CURLOPT_XFERINFOFUNCTION, NULL); + curl_easy_setopt(p->curl, CURLOPT_XFERINFODATA, NULL); +#endif + + luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.ud_ref); + p->pr.cb_ref = p->pr.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_POSTFIELDS(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_POSTFIELDS, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + curl_easy_setopt(p->curl, CURLOPT_POSTFIELDSIZE, -1); + lcurl_storage_remove_i(L, p->storage, CURLOPT_POSTFIELDS); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_SEEKFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SEEKFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_SEEKDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.ud_ref); + p->seek.cb_ref = p->seek.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_DEBUGFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_DEBUGFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_DEBUGDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.ud_ref); + p->debug.cb_ref = p->debug.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,19,6) + +static int lcurl_easy_unset_SSH_KEYFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SSH_KEYFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_SSH_KEYDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.ud_ref); + p->ssh_key.cb_ref = p->ssh_key.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,21,0) + +static int lcurl_easy_unset_FNMATCH_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_FNMATCH_FUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_FNMATCH_DATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->match.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->match.ud_ref); + p->match.cb_ref = p->match.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_CHUNK_BGN_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CHUNK_BGN_FUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + if(p->chunk_end.cb_ref == LUA_NOREF){ + // if other callback not set + curl_easy_setopt(p->curl, CURLOPT_CHUNK_DATA, NULL); + } + + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.ud_ref); + p->chunk_bgn.cb_ref = p->chunk_bgn.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_CHUNK_END_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CHUNK_END_FUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + if(p->chunk_bgn.cb_ref == LUA_NOREF){ + // if other callback not set + curl_easy_setopt(p->curl, CURLOPT_CHUNK_DATA, NULL); + } + + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.ud_ref); + p->chunk_end.cb_ref = p->chunk_end.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,46,0) + +static int lcurl_easy_unset_STREAM_DEPENDS(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_STREAM_DEPENDS, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_STREAM_DEPENDS); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_STREAM_DEPENDS_E(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_STREAM_DEPENDS_E, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_STREAM_DEPENDS_E); + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,56,0) + +static int lcurl_easy_unset_MIMEPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_MIMEPOST, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_MIMEPOST); + + p->mime = NULL; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,63,0) + +static int lcurl_easy_unset_CURLU(lua_State *L) { + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CURLU, NULL); + if (code != CURLE_OK) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_CURLU); + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,64,0) + +static int lcurl_easy_unset_TRAILERFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_TRAILERFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_TRAILERDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.ud_ref); + p->trailer.cb_ref = p->trailer.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + +static int lcurl_easy_unset_HSTSREADFUNCTION (lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HSTSREADFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_HSTSREADDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.ud_ref); + p->hstsread.cb_ref = p->hstsread.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_HSTSWRITEFUNCTION (lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HSTSWRITEFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_HSTSWRITEDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.ud_ref); + p->hstswrite.cb_ref = p->hstswrite.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#endif + +//} + +//} + +//{ info + +static int lcurl_info_get_long_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + long val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + +#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ + if(sizeof(lua_Integer) >= sizeof(val)) + lua_pushinteger(L, (lua_Integer)val); + else +#endif + lua_pushnumber(L, val); + + return 1; +} + +static int lcurl_info_get_double_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + double val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_pushnumber(L, val); + return 1; +} + +#if LCURL_CURL_VER_GE(7,55,0) + +static int lcurl_info_get_offset_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + curl_off_t val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lutil_pushint64(L, val); + return 1; +} + +#endif + +static int lcurl_info_get_string_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + char *val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_pushstring(L, val); + return 1; +} + +static int lcurl_info_get_slist_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + struct curl_slist *val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_util_slist_to_table(L, val); + curl_slist_free_all(val); + + return 1; +} + +static int lcurl_info_get_certinfo_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + int decode = lua_toboolean(L, 2); + struct curl_certinfo * val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_newtable(L); + { int i = 0; for(;inum_of_certs; ++i){ + struct curl_slist *slist = val->certinfo[i]; + if (decode) { + lua_newtable(L); + for(;slist; slist = slist->next){ + const char *ptr = strchr(slist->data, ':'); + if(ptr){ + lua_pushlstring(L, slist->data, ptr - slist->data); + lua_pushstring(L, ptr + 1); + lua_rawset(L, -3); + } + } + } + else{ + lcurl_util_slist_to_table(L, slist); + } + lua_rawseti(L, -2, i + 1); + }} + + return 1; +} + +#define LCURL_STR_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_string_(L, CURLINFO_##N); \ +} + +#define LCURL_LST_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_slist_(L, CURLINFO_##N);\ +} + +#define LCURL_LNG_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_long_(L, CURLINFO_##N);\ +} + +#define LCURL_DBL_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_double_(L, CURLINFO_##N);\ +} + +#define LCURL_OFF_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_offset_(L, CURLINFO_##N);\ +} + +#define LCURL_CERTINFO_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_certinfo_(L, CURLINFO_##N);\ +} + +#define OPT_ENTRY(L, N, T, S) LCURL_##T##_INFO(N, S) + +#include "lcinfoeasy.h" + +#undef OPT_ENTRY + +#undef LCURL_STR_INFO +#undef LCURL_LST_INFO +#undef LCURL_LNG_INFO +#undef LCURL_DBL_INFO + +//} + +//{ CallBack + +static int lcurl_easy_set_callback(lua_State *L, + lcurl_easy_t *p, lcurl_callback_t *c, + int OPT_CB, int OPT_UD, + const char *method, void *func +) +{ + CURLcode code; + lcurl_set_callback(L, c, 2, method); + + code = curl_easy_setopt(p->curl, OPT_CB, (c->cb_ref == LUA_NOREF)?0:func); + if((code != CURLE_OK)&&(c->cb_ref != LUA_NOREF)){ + luaL_unref(L, LCURL_LUA_REGISTRY, c->cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, c->ud_ref); + c->cb_ref = c->ud_ref = LUA_NOREF; + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, OPT_UD, (c->cb_ref == LUA_NOREF)?0:p); + + return 1; +} + +static size_t lcurl_write_callback_(lua_State*L, + lcurl_easy_t *p, lcurl_callback_t *c, + char *ptr, size_t size, size_t nmemb +){ + size_t ret = size * nmemb; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, c); + + lua_pushlstring(L, ptr, ret); + if(lua_pcall(L, n, LUA_MULTRET, 0)){ + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top+1); + return 0; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1)){ + if(lua_gettop(L) == (top+1)) lua_settop(L, top); + return 0; + } + if(lua_isnumber(L, top + 1)){ + ret = (size_t)lua_tonumber(L, top + 1); + } + else{ + if(!lua_toboolean(L, top + 1)) ret = 0; + } + } + + lua_settop(L, top); + return ret; +} + +//{ Writer + +static size_t lcurl_write_callback(char *ptr, size_t size, size_t nmemb, void *arg){ + lcurl_easy_t *p = arg; + assert(NULL != p->L); + return lcurl_write_callback_(p->L, p, &p->wr, ptr, size, nmemb); +} + +static int lcurl_easy_set_WRITEFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->wr, + CURLOPT_WRITEFUNCTION, CURLOPT_WRITEDATA, + "write", lcurl_write_callback + ); +} + +//} + +//{ Reader + +size_t lcurl_read_callback(lua_State *L, + lcurl_callback_t *rd, lcurl_read_buffer_t *rbuffer, + char *buffer, size_t size, size_t nitems +){ + const char *data; size_t data_size; + + size_t ret = size * nitems; + int n, top = lua_gettop(L); + + if(rbuffer->ref != LUA_NOREF){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, rbuffer->ref); + data = luaL_checklstring(L, -1, &data_size); + lua_pop(L, 1); + + data = data + rbuffer->off; + data_size -= rbuffer->off; + + if(data_size > ret){ + data_size = ret; + memcpy(buffer, data, data_size); + rbuffer->off += data_size; + } + else{ + memcpy(buffer, data, data_size); + luaL_unref(L, LCURL_LUA_REGISTRY, rbuffer->ref); + rbuffer->ref = LUA_NOREF; + } + + lua_settop(L, top); + return data_size; + } + + // buffer is clean + assert(rbuffer->ref == LUA_NOREF); + + n = lcurl_util_push_cb(L, rd); + lua_pushinteger(L, ret); + if(lua_pcall(L, n, LUA_MULTRET, 0)){ + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top+1); + return CURL_READFUNC_ABORT; + } + + if(lua_gettop(L) == top){ + return 0; + } + + assert(lua_gettop(L) >= top); + + if(lua_type(L, top + 1) != LUA_TSTRING){ + if(lua_isnil(L, top + 1)){ + if(lua_gettop(L) == (top+1)){// only nil -> EOF + lua_settop(L, top); + return 0; + } + } + else{ + if(lua_type(L, top + 1) == LUA_TNUMBER){ + size_t ret = lua_tointeger(L, top + 1); + if(ret == (size_t)CURL_READFUNC_PAUSE){ + lua_settop(L, top); + return CURL_READFUNC_PAUSE; + } + } + lua_settop(L, top); + } + return CURL_READFUNC_ABORT; + } + + data = lua_tolstring(L, top + 1, &data_size); + assert(data); + if(data_size > ret){ + data_size = ret; + rbuffer->ref = luaL_ref(L, LCURL_LUA_REGISTRY); + rbuffer->off = data_size; + } + memcpy(buffer, data, data_size); + + lua_settop(L, top); + return data_size; +} + +static size_t lcurl_easy_read_callback(char *buffer, size_t size, size_t nitems, void *arg){ + lcurl_easy_t *p = arg; + if(p->magic == LCURL_HPOST_STREAM_MAGIC){ + return lcurl_hpost_read_callback(buffer, size, nitems, arg); + } + assert(NULL != p->L); + return lcurl_read_callback(p->L, &p->rd, &p->rbuffer, buffer, size, nitems); +} + +static size_t lcurl_hpost_read_callback(char *buffer, size_t size, size_t nitems, void *arg){ + lcurl_hpost_stream_t *p = arg; + assert(NULL != p->L); + return lcurl_read_callback(*p->L, &p->rd, &p->rbuffer, buffer, size, nitems); +} + +static int lcurl_easy_set_READFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->rd, + CURLOPT_READFUNCTION, CURLOPT_READDATA, + "read", lcurl_easy_read_callback + ); +} + +//} + +//{ Header + +static size_t lcurl_header_callback(char *ptr, size_t size, size_t nmemb, void *arg){ + lcurl_easy_t *p = arg; + assert(NULL != p->L); + return lcurl_write_callback_(p->L, p, &p->hd, ptr, size, nmemb); +} + +static int lcurl_easy_set_HEADERFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->hd, + CURLOPT_HEADERFUNCTION, CURLOPT_HEADERDATA, + "header", lcurl_header_callback + ); +} + +//} + +//{ Progress + +static int lcurl_xferinfo_callback(void *arg, curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow) +{ + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int n, top, ret = 0; + + assert(NULL != p->L); + + top = lua_gettop(L); + n = lcurl_util_push_cb(L, &p->pr); + + lua_pushnumber( L, (lua_Number)dltotal ); + lua_pushnumber( L, (lua_Number)dlnow ); + lua_pushnumber( L, (lua_Number)ultotal ); + lua_pushnumber( L, (lua_Number)ulnow ); + + if(lua_pcall(L, n+3, LUA_MULTRET, 0)){ + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top+1); + return 1; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1)){ + if(lua_gettop(L) == (top+1)) lua_settop(L, top); + return 1; + } + if(lua_isboolean(L, top + 1)) + ret = lua_toboolean(L, top + 1)?0:1; + else{ + ret = lua_tonumber(L, top + 1); + #if LCURL_CURL_VER_GE(7,68,0) + if(ret != (size_t)CURL_PROGRESSFUNC_CONTINUE) + #endif + if(ret == 0) ret = 1; else ret = 0; + } + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_progress_callback(void *arg, double dltotal, double dlnow, + double ultotal, double ulnow) +{ + return lcurl_xferinfo_callback(arg, + (curl_off_t)dltotal, + (curl_off_t)dlnow, + (curl_off_t)ultotal, + (curl_off_t)ulnow + ); +} + +static int lcurl_easy_set_PROGRESSFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + int n = lcurl_easy_set_callback(L, p, &p->pr, + CURLOPT_PROGRESSFUNCTION, CURLOPT_PROGRESSDATA, + "progress", lcurl_progress_callback + ); + +#if LCURL_CURL_VER_GE(7,32,0) + if(p->pr.cb_ref != LUA_NOREF){ + curl_easy_setopt(p->curl, CURLOPT_XFERINFOFUNCTION, lcurl_xferinfo_callback); + curl_easy_setopt(p->curl, CURLOPT_XFERINFODATA, p); + } +#endif + + return n; +} + +//} + +//{ Seek + +static int lcurl_seek_callback(void *arg, curl_off_t offset, int origin){ + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int ret = CURL_SEEKFUNC_OK; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->seek); + + assert(NULL != p->L); + + if (SEEK_SET == origin) lua_pushliteral(L, "set"); + else if (SEEK_CUR == origin) lua_pushliteral(L, "cur"); + else if (SEEK_END == origin) lua_pushliteral(L, "end"); + else lua_pushinteger(L, origin); + lutil_pushint64(L, offset); + + if (lua_pcall(L, n+1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_SEEKFUNC_FAIL; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ + lua_settop(L, top + 2); + lua_remove(L, top + 1); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_SEEKFUNC_FAIL; + } + ret = lua_toboolean(L, top + 1) ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_easy_set_SEEKFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->seek, + CURLOPT_SEEKFUNCTION, CURLOPT_SEEKDATA, + "seek", lcurl_seek_callback + ); +} + +//} + +//{ Debug + +static int lcurl_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *arg){ + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->debug); + + assert(NULL != p->L); + assert(handle == p->curl); + + lua_pushinteger(L, type); + lua_pushlstring(L, data, size); + + // just ignore all errors from Lua callback + lua_pcall(L, n + 1, LUA_MULTRET, 0); + lua_settop(L, top); + + return 0; +} + +static int lcurl_easy_set_DEBUGFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->debug, + CURLOPT_DEBUGFUNCTION, CURLOPT_DEBUGDATA, + "debug", lcurl_debug_callback + ); +} + +//} + +//{ Match + +#if LCURL_CURL_VER_GE(7,21,0) + +static int lcurl_match_callback(void *arg, const char *pattern, const char *string) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int ret = CURL_FNMATCHFUNC_NOMATCH; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->match); + + assert(NULL != p->L); + + lua_pushstring(L, pattern); + lua_pushstring(L, string); + + if (lua_pcall(L, n+1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_FNMATCHFUNC_FAIL; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ + lua_settop(L, top + 2); + lua_remove(L, top + 1); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_FNMATCHFUNC_FAIL; + } + ret = lua_toboolean(L, top + 1) ? CURL_FNMATCHFUNC_MATCH : CURL_FNMATCHFUNC_NOMATCH; + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_easy_set_FNMATCH_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->match, + CURLOPT_FNMATCH_FUNCTION, CURLOPT_FNMATCH_DATA, + "match", lcurl_match_callback + ); +} + +#endif + +//} + +//{ Chunk begin/end + +#if LCURL_CURL_VER_GE(7,21,0) + +static int lcurl_chunk_bgn_callback(struct curl_fileinfo *info, void *arg, int remains) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int ret = CURL_CHUNK_BGN_FUNC_OK; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->chunk_bgn); + + assert(NULL != p->L); + + lua_newtable(L); + lua_pushstring (L, info->filename ); lua_setfield(L, -2, "filename" ); + lua_pushinteger(L, info->filetype ); lua_setfield(L, -2, "filetype" ); + lutil_pushint64(L, info->time ); lua_setfield(L, -2, "time" ); + lutil_pushint64(L, info->perm ); lua_setfield(L, -2, "perm" ); + lua_pushinteger(L, info->uid ); lua_setfield(L, -2, "uid" ); + lua_pushinteger(L, info->gid ); lua_setfield(L, -2, "gid" ); + lutil_pushint64(L, info->size ); lua_setfield(L, -2, "size" ); + lutil_pushint64(L, info->hardlinks ); lua_setfield(L, -2, "hardlinks" ); + lutil_pushint64(L, info->flags ); lua_setfield(L, -2, "flags" ); + + lua_newtable(L); + if(info->strings.time) { lua_pushstring (L, info->strings.time ); lua_setfield(L, -2, "time" ); } + if(info->strings.perm) { lua_pushstring (L, info->strings.perm ); lua_setfield(L, -2, "perm" ); } + if(info->strings.user) { lua_pushstring (L, info->strings.user ); lua_setfield(L, -2, "user" ); } + if(info->strings.group) { lua_pushstring (L, info->strings.group ); lua_setfield(L, -2, "group" ); } + if(info->strings.target){ lua_pushstring (L, info->strings.target); lua_setfield(L, -2, "target"); } + lua_setfield(L, -2, "strings"); + + lua_pushinteger(L, remains); + + if (lua_pcall(L, n+1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_CHUNK_BGN_FUNC_FAIL; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ + lua_settop(L, top + 2); + lua_remove(L, top + 1); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_CHUNK_BGN_FUNC_FAIL; + } + ret = lua_toboolean(L, top + 1) ? CURL_CHUNK_BGN_FUNC_OK : CURL_CHUNK_BGN_FUNC_SKIP; + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_chunk_end_callback(void *arg) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int ret = CURL_CHUNK_END_FUNC_OK; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->chunk_end); + + assert(NULL != p->L); + + if (lua_pcall(L, n-1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_CHUNK_END_FUNC_FAIL; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ + lua_settop(L, top + 2); + lua_remove(L, top + 1); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_CHUNK_END_FUNC_FAIL; + } + ret = lua_toboolean(L, top + 1) ? CURL_CHUNK_END_FUNC_OK : CURL_CHUNK_END_FUNC_FAIL; + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_easy_set_CHUNK_BGN_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->chunk_bgn, + CURLOPT_CHUNK_BGN_FUNCTION, CURLOPT_CHUNK_DATA, + "chunk_bgn", lcurl_chunk_bgn_callback + ); +} + +static int lcurl_easy_set_CHUNK_END_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->chunk_end, + CURLOPT_CHUNK_END_FUNCTION, CURLOPT_CHUNK_DATA, + "chunk_end", lcurl_chunk_end_callback + ); +} + +#endif + +//} + +//{ Trailer + +#if LCURL_CURL_VER_GE(7,64,0) + +static int lcurl_trailer_callback(struct curl_slist **list, void *arg) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->trailer); + + if (lua_pcall(L, n - 1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_TRAILERFUNC_ABORT; + } + + n = lua_gettop(L); + + if (n == top) { + return CURL_TRAILERFUNC_OK; + } + + /* libcurl will free the list */ + *list = lcurl_util_to_slist(L, top + 1); + if (*list) { + lua_settop(L, top); + return CURL_TRAILERFUNC_OK; + } + + // empty array or NULL + if (lua_istable(L, top + 1) || lutil_is_null(L, top + 1)) { + lua_settop(L, top); + return CURL_TRAILERFUNC_OK; + } + + // true + if((lua_type(L, top + 1) == LUA_TBOOLEAN) && (lua_toboolean(L, top + 1))){ + lua_settop(L, top); + return CURL_TRAILERFUNC_OK; + } + + // single nil + if((n == (top + 1)) && lua_isnil(L, top + 1)){ + lua_settop(L, top); + return CURL_TRAILERFUNC_OK; + } + + lua_settop(L, top); + return CURL_TRAILERFUNC_ABORT; +} + +static int lcurl_easy_set_TRAILERFUNCTION (lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->trailer, + CURLOPT_TRAILERFUNCTION, CURLOPT_TRAILERDATA, + "trailer", lcurl_trailer_callback + ); +} + +#endif + +//} + +//{ HSTS Reader + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + +#define LCURL_HSTS_EXPIRE_LEN 18 + +static int lcurl_hstsread_callback(CURL *easy, struct curl_hstsentry *sts, void *arg) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->hstsread); + const char *name; size_t namelen; + const char *expire; size_t expirelen; + int type; + + assert(NULL != p->L); + + lua_pushinteger(L, sts->namelen); + if (lua_pcall(L, n, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURLSTS_FAIL; + } + + if (lua_gettop(L) == top) { + return CURLSTS_DONE; + } + + assert(lua_gettop(L) >= top); + + type = lua_type(L, top + 1); + if (type == LUA_TNIL) { + lua_settop(L, top); + return CURLSTS_DONE; + } + + if (type != LUA_TSTRING) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + name = lua_tolstring(L, top + 1, &namelen); + + if(namelen > sts->namelen) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + memcpy(sts->name, name, namelen + 1); + + type = lua_type(L, top + 2); + if (type == LUA_TNONE) { + lua_settop(L, top); + return CURLSTS_OK; + } + + if ((type != LUA_TBOOLEAN) && (type != LUA_TNIL)) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + if (type == LUA_TBOOLEAN) { + sts->includeSubDomains = lua_toboolean(L, top + 2) ? 0 : 1; + } + else if (type != LUA_TNIL) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + type = lua_type(L, top + 3); + if ((type == LUA_TNONE) || (type == LUA_TNIL)) { + lua_settop(L, top); + return CURLSTS_OK; + } + + if(type != LUA_TSTRING) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + expire = lua_tolstring(L, top + 3, &expirelen); + + if (expirelen != LCURL_HSTS_EXPIRE_LEN - 1) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + memcpy(sts->expire, expire, expirelen + 1); + + lua_settop(L, top); + return CURLSTS_OK; +} + +static int lcurl_easy_set_HSTSREADFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->hstsread, + CURLOPT_HSTSREADFUNCTION, CURLOPT_HSTSREADDATA, + "hstsread", lcurl_hstsread_callback + ); +} + +#endif + +//} + +//{ HSTS Writer + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + +static int lcurl_hstswrite_callback(CURL *easy, struct curl_hstsentry *sts, struct curl_index *count, void *arg) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->hstswrite); + int type; + + assert(NULL != p->L); + + lua_pushstring(L, sts->name); + lua_pushboolean(L, sts->includeSubDomains ? 1 : 0); + if (sts->expire[0]) { + lua_pushstring(L, sts->expire); + } else { + lua_pushnil(L); + } + lua_pushinteger(L, count->index); + lua_pushinteger(L, count->total); + + if (lua_pcall(L, n + 4, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURLSTS_FAIL; + } + + if (lua_gettop(L) == top) { + return CURLSTS_OK; + } + + assert(lua_gettop(L) >= top); + + type = lua_type(L, top + 1); + if (type == LUA_TNIL) { + type = lua_type(L, top + 2); + lua_settop(L, top); + if(type == LUA_TNONE){ + return CURLSTS_OK; + } + return CURLSTS_FAIL; + } + + if (type == LUA_TNUMBER) { + int ret = lua_tointeger(L, top + 1); + lua_settop(L, top); + return ret; + } + + if (type == LUA_TBOOLEAN) { + int ret = lua_toboolean(L, top + 1); + lua_settop(L, top); + return ret ? CURLSTS_OK : CURLSTS_DONE; + } + + lua_settop(L, top); + return CURLSTS_FAIL; +} + +static int lcurl_easy_set_HSTSWRITEFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->hstswrite, + CURLOPT_HSTSWRITEFUNCTION, CURLOPT_HSTSWRITEDATA, + "hstswrite", lcurl_hstswrite_callback + ); +} + +#endif + +//} + +//{ SSH key + +#if LCURL_CURL_VER_GE(7,19,6) + +static void lcurl_ssh_key_push(lua_State *L, const struct curl_khkey *key){ + if (!key) { + lua_pushnil(L); + return; + } + + lua_newtable(L); + + if(key->len){ + lua_pushliteral(L, "raw"); + lua_pushlstring(L, key->key, key->len); + } else { + lua_pushliteral(L, "base64"); + lua_pushstring(L, key->key); + } + lua_rawset(L, -3); + + lua_pushliteral(L, "type"); + lutil_pushuint(L, key->keytype); + lua_rawset(L, -3); +} + +static int lcurl_ssh_key_callback( + CURL *easy, + const struct curl_khkey *knownkey, + const struct curl_khkey *foundkey, + enum curl_khmatch khmatch, + void *arg +) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->ssh_key); + + assert(NULL != p->L); + + lcurl_ssh_key_push(L, knownkey); + lcurl_ssh_key_push(L, foundkey); + lutil_pushuint(L, khmatch); + + if (lua_pcall(L, n + 2, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURLKHSTAT_REJECT; + } + + if (lua_gettop(L) > top) { + int ret = lua_tointeger(L, top + 1); + lua_settop(L, top); + + switch (ret) +#if LCURL_CURL_VER_GE(7,73,0) + case CURLKHSTAT_FINE_REPLACE: +#endif + case CURLKHSTAT_FINE_ADD_TO_FILE: + case CURLKHSTAT_FINE: + case CURLKHSTAT_REJECT: + case CURLKHSTAT_DEFER: + return ret; + } + + return CURLKHSTAT_REJECT; +} + +static int lcurl_easy_set_SSH_KEYFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->ssh_key, + CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA, + "ssh_key", lcurl_ssh_key_callback + ); +} + +#endif + +//} + +static int lcurl_easy_setopt(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + long opt; + + luaL_checkany(L, 2); + if(lua_type(L, 2) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 2, 1, 0, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); + if(ret) return ret; + lua_settop(L, 1); + return 1; + } + + opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S, D) case CURLOPT_##N: return lcurl_easy_set_##N(L); + switch(opt){ + #include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); +} + +static int lcurl_easy_unsetopt(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + long opt; + + opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S, D) case CURLOPT_##N: return lcurl_easy_unset_##N(L); + switch(opt){ + #include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); +} + +static int lcurl_easy_getinfo(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + long opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S) case CURLINFO_##N: return lcurl_easy_get_##N(L); + switch(opt){ + #include "lcinfoeasy.h" + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); +} + +static int lcurl_easy_pause(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lua_State *curL; + int mask = luaL_checkint(L, 2); + CURLcode code; + + curL = p->L; lcurl__easy_assign_lua(L, p, L, 1); + code = curl_easy_pause(p->curl, mask); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__easy_assign_lua(L, p, curL, 1); + + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_setdata(lua_State *L){ + lua_settop(L, 2); + lua_pushvalue(L, 1); + lua_insert(L, 2); + lua_rawset(L, LCURL_USERVALUES); + return 1; +} + +static int lcurl_easy_getdata(lua_State *L){ + lua_settop(L, 1); + lua_rawget(L, LCURL_USERVALUES); + return 1; +} + +//} + +static const struct luaL_Reg lcurl_easy_methods[] = { + +#define OPT_ENTRY(L, N, T, S, D) { "setopt_"#L, lcurl_easy_set_##N }, + #include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif +#undef OPT_ENTRY + +#define OPT_ENTRY(L, N, T, S, D) { "unsetopt_"#L, lcurl_easy_unset_##N }, + #include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif +#undef OPT_ENTRY + +#define OPT_ENTRY(L, N, T, S) { "getinfo_"#L, lcurl_easy_get_##N }, + #include "lcinfoeasy.h" +#undef OPT_ENTRY + +#if LCURL_CURL_VER_GE(7,56,0) + { "mime", lcurl_easy_mime }, +#endif + + { "pause", lcurl_easy_pause }, + { "reset", lcurl_easy_reset }, + { "setopt", lcurl_easy_setopt }, + { "getinfo", lcurl_easy_getinfo }, + { "unsetopt", lcurl_easy_unsetopt }, + { "escape", lcurl_easy_escape }, + { "unescape", lcurl_easy_unescape }, + { "perform", lcurl_easy_perform }, +#if LCURL_CURL_VER_GE(7,62,0) + { "upkeep", lcurl_easy_upkeep }, +#endif + { "close", lcurl_easy_cleanup }, + { "__gc", lcurl_easy_cleanup }, + { "__tostring", lcurl_easy_to_s }, + + { "setdata", lcurl_easy_setdata }, + { "getdata", lcurl_easy_getdata }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_easy_opt[] = { + +#define OPT_ENTRY(L, N, T, S, D) { "OPT_"#N, CURLOPT_##N }, +#define FLG_ENTRY(N) { #N, CURL_##N }, +#include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif +#undef OPT_ENTRY +#undef FLG_ENTRY + +#define OPT_ENTRY(L, N, T, S) { "INFO_"#N, CURLINFO_##N }, +#include "lcinfoeasy.h" +#undef OPT_ENTRY + +#define OPT_ENTRY(N) { #N, CURL##N }, + // Debug message types not easy info + OPT_ENTRY(INFO_TEXT ) + OPT_ENTRY(INFO_HEADER_IN ) + OPT_ENTRY(INFO_HEADER_OUT ) + OPT_ENTRY(INFO_DATA_IN ) + OPT_ENTRY(INFO_DATA_OUT ) + OPT_ENTRY(INFO_SSL_DATA_OUT ) + OPT_ENTRY(INFO_SSL_DATA_IN ) + + // File types for CURL_CHUNK_BGN_FUNCTION +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(FILETYPE_DEVICE_BLOCK ) + OPT_ENTRY(FILETYPE_DEVICE_CHAR ) + OPT_ENTRY(FILETYPE_DIRECTORY ) + OPT_ENTRY(FILETYPE_DOOR ) + OPT_ENTRY(FILETYPE_FILE ) + OPT_ENTRY(FILETYPE_NAMEDPIPE ) + OPT_ENTRY(FILETYPE_SOCKET ) + OPT_ENTRY(FILETYPE_SYMLINK ) + OPT_ENTRY(FILETYPE_UNKNOWN ) +#endif + +#undef OPT_ENTRY + + {NULL, 0} +}; + +void lcurl_easy_initlib(lua_State *L, int nup){ + + /* Hack. We ensure that lcurl_easy_t and lcurl_hpost_stream_t + compatiable for readfunction + */ + LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, magic, lcurl_hpost_stream_t, magic); + LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, L, lcurl_hpost_stream_t, L); + LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, rd, lcurl_hpost_stream_t, rd); + LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, rbuffer, lcurl_hpost_stream_t, rbuffer); + LCURL_ASSERT_SAME_FIELD_SIZE(lcurl_easy_t, rbuffer, lcurl_hpost_stream_t, rbuffer); + + if(!lutil_createmetap(L, LCURL_EASY, lcurl_easy_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_easy_opt); +} + +//} diff --git a/watchdog/third_party/lua-curl/src/lceasy.h b/watchdog/third_party/lua-curl/src/lceasy.h new file mode 100644 index 0000000..c03617c --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lceasy.h @@ -0,0 +1,127 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCEASY_H_ +#define _LCEASY_H_ + +#include "lcurl.h" +#include "lcutils.h" +#include "lchttppost.h" + +#define LCURL_LST_INDEX(N) LCURL_##N##_LIST, +#define LCURL_STR_INDEX(N) +#define LCURL_LNG_INDEX(N) +#define LCURL_OFF_INDEX(N) +#define LCURL_BLB_INDEX(N) +#define OPT_ENTRY(L, N, T, S, D) LCURL_##T##_INDEX(N) + +enum { + LCURL_LIST_DUMMY = -1, + +#include"lcopteasy.h" + + LCURL_LIST_COUNT, +}; + +#undef LCURL_BLB_INDEX +#undef LCURL_OFF_INDEX +#undef LCURL_LST_INDEX +#undef LCURL_STR_INDEX +#undef LCURL_LNG_INDEX +#undef OPT_ENTRY + +#define LCURL_EASY_MAGIC 0xEA + +#if LCURL_CC_SUPPORT_FORWARD_TYPEDEF + typedef struct lcurl_multi_tag lcurl_multi_t; + #if LCURL_CURL_VER_GE(7,56,0) + typedef struct lcurl_mime_tag lcurl_mime_t; + #endif + #if LCURL_CURL_VER_GE(7,63,0) + typedef struct lcurl_url_tag lcurl_url_t; + #endif +#else + struct lcurl_multi_tag; + #define lcurl_multi_t struct lcurl_multi_tag + #if LCURL_CURL_VER_GE(7,56,0) + struct lcurl_mime_tag; + #define lcurl_mime_t struct lcurl_mime_tag + #endif + #if LCURL_CURL_VER_GE(7,63,0) + struct lcurl_url_tag; + #define lcurl_url_t struct lcurl_url_tag + #endif +#endif + +typedef struct lcurl_easy_tag{ + unsigned char magic; + + lua_State *L; + lcurl_callback_t rd; + lcurl_read_buffer_t rbuffer; + + lcurl_hpost_t *post; + + lcurl_multi_t *multi; + +#if LCURL_CURL_VER_GE(7,56,0) + lcurl_mime_t *mime; +#endif + + CURL *curl; + int storage; + int lists[LCURL_LIST_COUNT]; + int err_mode; + lcurl_callback_t wr; + lcurl_callback_t hd; + lcurl_callback_t pr; + lcurl_callback_t seek; + lcurl_callback_t debug; + lcurl_callback_t match; + lcurl_callback_t chunk_bgn; + lcurl_callback_t chunk_end; +#if LCURL_CURL_VER_GE(7,19,6) + lcurl_callback_t ssh_key; +#endif +#if LCURL_CURL_VER_GE(7,64,0) + lcurl_callback_t trailer; +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + lcurl_callback_t hstsread; + lcurl_callback_t hstswrite; +#endif +}lcurl_easy_t; + +int lcurl_easy_create(lua_State *L, int error_mode); + +lcurl_easy_t *lcurl_geteasy_at(lua_State *L, int i); + +#define lcurl_geteasy(L) lcurl_geteasy_at((L),1) + +void lcurl_easy_initlib(lua_State *L, int nup); + +void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi); + +size_t lcurl_read_callback(lua_State *L, + lcurl_callback_t *rd, lcurl_read_buffer_t *rbuffer, + char *buffer, size_t size, size_t nitems +); + +#if !LCURL_CC_SUPPORT_FORWARD_TYPEDEF +#undef lcurl_multi_t +#ifdef lcurl_mime_t +#undef lcurl_mime_t +#endif +#ifdef lcurl_url_t +#undef lcurl_url_t +#endif +#endif + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_easy.h b/watchdog/third_party/lua-curl/src/lcerr_easy.h new file mode 100644 index 0000000..986026a --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_easy.h @@ -0,0 +1,146 @@ +ERR_ENTRY ( OK ) +ERR_ENTRY ( UNSUPPORTED_PROTOCOL ) +ERR_ENTRY ( FAILED_INIT ) +ERR_ENTRY ( URL_MALFORMAT ) +#if LCURL_CURL_VER_GE(7,21,5) +ERR_ENTRY ( NOT_BUILT_IN ) +#endif +ERR_ENTRY ( COULDNT_RESOLVE_PROXY ) +ERR_ENTRY ( COULDNT_RESOLVE_HOST ) +ERR_ENTRY ( COULDNT_CONNECT ) +#if LCURL_CURL_VER_GE(7,51,0) +ERR_ENTRY ( WEIRD_SERVER_REPLY ) +#else +ERR_ENTRY ( FTP_WEIRD_SERVER_REPLY ) +#endif +ERR_ENTRY ( REMOTE_ACCESS_DENIED ) +#if LCURL_CURL_VER_GE(7,31,0) +ERR_ENTRY ( FTP_ACCEPT_FAILED ) +#endif +ERR_ENTRY ( FTP_WEIRD_PASS_REPLY ) +#if LCURL_CURL_VER_GE(7,24,0) +ERR_ENTRY ( FTP_ACCEPT_TIMEOUT ) +#endif +ERR_ENTRY ( FTP_WEIRD_PASV_REPLY ) +ERR_ENTRY ( FTP_WEIRD_227_FORMAT ) +ERR_ENTRY ( FTP_CANT_GET_HOST ) +ERR_ENTRY ( FTP_COULDNT_SET_TYPE ) +ERR_ENTRY ( PARTIAL_FILE ) +ERR_ENTRY ( FTP_COULDNT_RETR_FILE ) +ERR_ENTRY ( OBSOLETE20 ) +ERR_ENTRY ( QUOTE_ERROR ) +ERR_ENTRY ( HTTP_RETURNED_ERROR ) +ERR_ENTRY ( WRITE_ERROR ) +ERR_ENTRY ( OBSOLETE24 ) +ERR_ENTRY ( UPLOAD_FAILED ) +ERR_ENTRY ( READ_ERROR ) +ERR_ENTRY ( OUT_OF_MEMORY ) +ERR_ENTRY ( OPERATION_TIMEDOUT ) +ERR_ENTRY ( OBSOLETE29 ) +ERR_ENTRY ( FTP_PORT_FAILED ) +ERR_ENTRY ( FTP_COULDNT_USE_REST ) +ERR_ENTRY ( OBSOLETE32 ) +ERR_ENTRY ( RANGE_ERROR ) +ERR_ENTRY ( HTTP_POST_ERROR ) +ERR_ENTRY ( SSL_CONNECT_ERROR ) +ERR_ENTRY ( BAD_DOWNLOAD_RESUME ) +ERR_ENTRY ( FILE_COULDNT_READ_FILE ) +ERR_ENTRY ( LDAP_CANNOT_BIND ) +ERR_ENTRY ( LDAP_SEARCH_FAILED ) +ERR_ENTRY ( OBSOLETE40 ) +ERR_ENTRY ( FUNCTION_NOT_FOUND ) +ERR_ENTRY ( ABORTED_BY_CALLBACK ) +ERR_ENTRY ( BAD_FUNCTION_ARGUMENT ) +ERR_ENTRY ( OBSOLETE44 ) +ERR_ENTRY ( INTERFACE_FAILED ) +ERR_ENTRY ( OBSOLETE46 ) +ERR_ENTRY ( TOO_MANY_REDIRECTS ) +#if LCURL_CURL_VER_GE(7,21,5) +ERR_ENTRY ( UNKNOWN_OPTION ) +#else +ERR_ENTRY ( UNKNOWN_TELNET_OPTION ) /* User specified an unknown option */ +#endif +ERR_ENTRY ( TELNET_OPTION_SYNTAX ) +ERR_ENTRY ( OBSOLETE50 ) +ERR_ENTRY ( PEER_FAILED_VERIFICATION ) +ERR_ENTRY ( GOT_NOTHING ) +ERR_ENTRY ( SSL_ENGINE_NOTFOUND ) +ERR_ENTRY ( SSL_ENGINE_SETFAILED ) +ERR_ENTRY ( SEND_ERROR ) +ERR_ENTRY ( RECV_ERROR ) +ERR_ENTRY ( OBSOLETE57 ) +ERR_ENTRY ( SSL_CERTPROBLEM ) +ERR_ENTRY ( SSL_CIPHER ) +#if LCURL_CURL_VER_GE(7,62,0) +ERR_ENTRY ( OBSOLETE51 ) +#else +ERR_ENTRY ( SSL_CACERT ) +#endif +ERR_ENTRY ( BAD_CONTENT_ENCODING ) +ERR_ENTRY ( LDAP_INVALID_URL ) +ERR_ENTRY ( FILESIZE_EXCEEDED ) +ERR_ENTRY ( USE_SSL_FAILED ) +ERR_ENTRY ( SEND_FAIL_REWIND ) +ERR_ENTRY ( SSL_ENGINE_INITFAILED ) +ERR_ENTRY ( LOGIN_DENIED ) +ERR_ENTRY ( TFTP_NOTFOUND ) +ERR_ENTRY ( TFTP_PERM ) +ERR_ENTRY ( REMOTE_DISK_FULL ) +ERR_ENTRY ( TFTP_ILLEGAL ) +ERR_ENTRY ( TFTP_UNKNOWNID ) +ERR_ENTRY ( REMOTE_FILE_EXISTS ) +ERR_ENTRY ( TFTP_NOSUCHUSER ) +ERR_ENTRY ( CONV_FAILED ) +ERR_ENTRY ( CONV_REQD ) +ERR_ENTRY ( SSL_CACERT_BADFILE ) +ERR_ENTRY ( REMOTE_FILE_NOT_FOUND ) +ERR_ENTRY ( SSH ) +ERR_ENTRY ( SSL_SHUTDOWN_FAILED ) +ERR_ENTRY ( AGAIN ) +ERR_ENTRY ( SSL_CRL_BADFILE ) +ERR_ENTRY ( SSL_ISSUER_ERROR ) +#if LCURL_CURL_VER_GE(7,20,0) +ERR_ENTRY ( FTP_PRET_FAILED ) +#endif +#if LCURL_CURL_VER_GE(7,21,0) +ERR_ENTRY ( FTP_BAD_FILE_LIST ) +#endif +#if LCURL_CURL_VER_GE(7,20,0) +ERR_ENTRY ( RTSP_CSEQ_ERROR ) +ERR_ENTRY ( RTSP_SESSION_ERROR ) +#endif +#if LCURL_CURL_VER_GE(7,21,0) +ERR_ENTRY ( CHUNK_FAILED ) +#endif +#if LCURL_CURL_VER_GE(7,30,0) +ERR_ENTRY ( NO_CONNECTION_AVAILABLE ) +#endif +#if LCURL_CURL_VER_GE(7,38,0) +ERR_ENTRY ( HTTP2 ) +#else +ERR_ENTRY ( OBSOLETE16 ) +#endif +#if LCURL_CURL_VER_GE(7,39,0) +ERR_ENTRY ( SSL_PINNEDPUBKEYNOTMATCH ) +#endif +#if LCURL_CURL_VER_GE(7,41,0) +ERR_ENTRY ( SSL_INVALIDCERTSTATUS ) +#endif +#if LCURL_CURL_VER_GE(7,49,0) +ERR_ENTRY ( HTTP2_STREAM ) +#endif +#if LCURL_CURL_VER_GE(7,59,0) +ERR_ENTRY ( RECURSIVE_API_CALL ) +#endif +#if LCURL_CURL_VER_GE(7,66,0) +ERR_ENTRY ( AUTH_ERROR ) +#endif +#if LCURL_CURL_VER_GE(7,68,0) +ERR_ENTRY ( HTTP3 ) +#endif +#if LCURL_CURL_VER_GE(7,69,0) +ERR_ENTRY ( QUIC_CONNECT_ERROR ) +#endif +#if LCURL_CURL_VER_GE(7,73,0) +ERR_ENTRY ( PROXY ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_form.h b/watchdog/third_party/lua-curl/src/lcerr_form.h new file mode 100644 index 0000000..60ca8ce --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_form.h @@ -0,0 +1,8 @@ +ERR_ENTRY ( OK ) +ERR_ENTRY ( MEMORY ) +ERR_ENTRY ( OPTION_TWICE ) +ERR_ENTRY ( NULL ) +ERR_ENTRY ( UNKNOWN_OPTION ) +ERR_ENTRY ( INCOMPLETE ) +ERR_ENTRY ( ILLEGAL_ARRAY ) +ERR_ENTRY ( DISABLED ) diff --git a/watchdog/third_party/lua-curl/src/lcerr_multi.h b/watchdog/third_party/lua-curl/src/lcerr_multi.h new file mode 100644 index 0000000..45322b7 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_multi.h @@ -0,0 +1,14 @@ +ERR_ENTRY ( OK ) +ERR_ENTRY ( CALL_MULTI_PERFORM ) +ERR_ENTRY ( BAD_HANDLE ) +ERR_ENTRY ( BAD_EASY_HANDLE ) +ERR_ENTRY ( OUT_OF_MEMORY ) +ERR_ENTRY ( INTERNAL_ERROR ) +ERR_ENTRY ( BAD_SOCKET ) +ERR_ENTRY ( UNKNOWN_OPTION ) +#if LCURL_CURL_VER_GE(7,32,1) +ERR_ENTRY ( ADDED_ALREADY ) +#endif +#if LCURL_CURL_VER_GE(7,59,0) +ERR_ENTRY ( RECURSIVE_API_CALL ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_share.h b/watchdog/third_party/lua-curl/src/lcerr_share.h new file mode 100644 index 0000000..7027c2a --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_share.h @@ -0,0 +1,8 @@ +ERR_ENTRY ( OK ) +ERR_ENTRY ( BAD_OPTION ) +ERR_ENTRY ( IN_USE ) +ERR_ENTRY ( INVALID ) +ERR_ENTRY ( NOMEM ) +#if LCURL_CURL_VER_GE(7,23,0) +ERR_ENTRY ( NOT_BUILT_IN ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_url.h b/watchdog/third_party/lua-curl/src/lcerr_url.h new file mode 100644 index 0000000..f1002a1 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_url.h @@ -0,0 +1,20 @@ +#if LCURL_CURL_VER_GE(7,61,0) +ERR_ENTRY ( BAD_HANDLE ) +ERR_ENTRY ( BAD_PARTPOINTER ) +ERR_ENTRY ( BAD_PORT_NUMBER ) +ERR_ENTRY ( MALFORMED_INPUT ) +ERR_ENTRY ( NO_FRAGMENT ) +ERR_ENTRY ( NO_HOST ) +ERR_ENTRY ( NO_OPTIONS ) +ERR_ENTRY ( NO_PASSWORD ) +ERR_ENTRY ( NO_PORT ) +ERR_ENTRY ( NO_QUERY ) +ERR_ENTRY ( NO_SCHEME ) +ERR_ENTRY ( NO_USER ) +ERR_ENTRY ( OK ) +ERR_ENTRY ( OUT_OF_MEMORY ) +ERR_ENTRY ( UNKNOWN_PART ) +ERR_ENTRY ( UNSUPPORTED_SCHEME) +ERR_ENTRY ( URLDECODE ) +ERR_ENTRY ( USER_NOT_ALLOWED ) +#endif \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/src/lcerror.c b/watchdog/third_party/lua-curl/src/lcerror.c new file mode 100644 index 0000000..11eea55 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerror.c @@ -0,0 +1,342 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lcerror.h" +#include +#include "lcutils.h" + +#define LCURL_ERROR_NAME LCURL_PREFIX" Error" +static const char *LCURL_ERROR = LCURL_ERROR_NAME; + +#define LCURL_ERROR_EASY_NAME "CURL-EASY" +#define LCURL_ERROR_MULTI_NAME "CURL-MULTI" +#define LCURL_ERROR_SHARE_NAME "CURL-SHARE" +#define LCURL_ERROR_FORM_NAME "CURL-FORM" +#define LCURL_ERROR_URL_NAME "CURL-URL" + +typedef struct lcurl_error_tag{ + int tp; + int no; +}lcurl_error_t; + +//{ + +static const char* lcurl_err_easy_mnemo(int err){ +#define ERR_ENTRY(E) case CURLE_##E: return #E; + + switch (err){ + #include "lcerr_easy.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* lcurl_err_multi_mnemo(int err){ +#define ERR_ENTRY(E) case CURLM_##E: return #E; + + switch (err){ + #include "lcerr_multi.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* lcurl_err_share_mnemo(int err){ +#define ERR_ENTRY(E) case CURLSHE_##E: return #E; + + switch (err){ + #include "lcerr_share.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* lcurl_err_form_mnemo(int err){ +#define ERR_ENTRY(E) case CURL_FORMADD_##E: return #E; + + switch (err){ + #include "lcerr_form.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* lcurl_err_url_mnemo(int err){ +#define ERR_ENTRY(E) case CURLUE_##E: return #E; + + switch (err){ + #include "lcerr_url.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* _lcurl_err_mnemo(int tp, int err){ + switch(tp){ + case LCURL_ERROR_EASY : return lcurl_err_easy_mnemo (err); + case LCURL_ERROR_MULTI: return lcurl_err_multi_mnemo(err); + case LCURL_ERROR_SHARE: return lcurl_err_share_mnemo(err); + case LCURL_ERROR_FORM : return lcurl_err_form_mnemo (err); + case LCURL_ERROR_URL : return lcurl_err_url_mnemo (err); + } + assert(0); + return ""; +} + +static const char* _lcurl_err_msg(int tp, int err){ + switch(tp){ + case LCURL_ERROR_EASY : return curl_easy_strerror (err); + case LCURL_ERROR_MULTI: return curl_multi_strerror(err); + case LCURL_ERROR_SHARE: return curl_share_strerror(err); + case LCURL_ERROR_FORM : return lcurl_err_form_mnemo(err); + case LCURL_ERROR_URL : return lcurl_err_url_mnemo(err); + } + assert(0); + return ""; +} + +static const char* _lcurl_err_category_name(int tp){ + assert( + (tp == LCURL_ERROR_EASY ) || + (tp == LCURL_ERROR_MULTI) || + (tp == LCURL_ERROR_SHARE) || + (tp == LCURL_ERROR_FORM ) || + (tp == LCURL_ERROR_URL ) || + 0 + ); + + switch(tp){ + case LCURL_ERROR_EASY: { + static const char *name = LCURL_ERROR_EASY_NAME; + return name; + } + case LCURL_ERROR_MULTI: { + static const char *name = LCURL_ERROR_MULTI_NAME; + return name; + } + case LCURL_ERROR_SHARE: { + static const char *name = LCURL_ERROR_SHARE_NAME; + return name; + } + case LCURL_ERROR_FORM: { + static const char *name = LCURL_ERROR_FORM_NAME; + return name; + } + case LCURL_ERROR_URL: { + static const char *name = LCURL_ERROR_URL_NAME; + return name; + } + } + + assert(0); + return NULL; +} + +static void _lcurl_err_pushstring(lua_State *L, int tp, int err){ + lua_pushfstring(L, "[%s][%s] %s (%d)", + _lcurl_err_category_name(tp), + _lcurl_err_mnemo(tp, err), + _lcurl_err_msg(tp, err), + err + ); +} + +//} + +//{ + +int lcurl_error_create(lua_State *L, int error_type, int no){ + lcurl_error_t *err = lutil_newudatap(L, lcurl_error_t, LCURL_ERROR); + + assert( + (error_type == LCURL_ERROR_EASY ) || + (error_type == LCURL_ERROR_MULTI) || + (error_type == LCURL_ERROR_SHARE) || + (error_type == LCURL_ERROR_FORM ) || + (error_type == LCURL_ERROR_URL ) || + 0 + ); + + err->tp = error_type; + err->no = no; + return 1; +} + +static lcurl_error_t *lcurl_geterror_at(lua_State *L, int i){ + lcurl_error_t *err = (lcurl_error_t *)lutil_checkudatap (L, i, LCURL_ERROR); + luaL_argcheck (L, err != NULL, 1, LCURL_PREFIX"error object expected"); + return err; +} + +#define lcurl_geterror(L) lcurl_geterror_at((L),1) + +static int lcurl_err_no(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + lua_pushinteger(L, err->no); + return 1; +} + +static int lcurl_err_msg(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + lua_pushstring(L, _lcurl_err_msg(err->tp, err->no)); + return 1; +} + +static int lcurl_err_mnemo(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + lua_pushstring(L, _lcurl_err_mnemo(err->tp, err->no)); + return 1; +} + +static int lcurl_err_tostring(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + _lcurl_err_pushstring(L, err->tp, err->no); + return 1; +} + +static int lcurl_err_equal(lua_State *L){ + lcurl_error_t *lhs = lcurl_geterror_at(L, 1); + lcurl_error_t *rhs = lcurl_geterror_at(L, 2); + lua_pushboolean(L, ((lhs->no == rhs->no)&&(lhs->tp == rhs->tp))?1:0); + return 1; +} + +static int lcurl_err_category(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + lua_pushstring(L, _lcurl_err_category_name(err->tp)); + return 1; +} + +//} + +//{ + +int lcurl_fail_ex(lua_State *L, int mode, int error_type, int code){ + if(mode == LCURL_ERROR_RETURN){ + lua_pushnil(L); + lcurl_error_create(L, error_type, code); + return 2; + } + +#if LUA_VERSION_NUM >= 502 // lua 5.2 + lcurl_error_create(L, error_type, code); +#else + _lcurl_err_pushstring(L, error_type, code); +#endif + + assert(LCURL_ERROR_RAISE == mode); + + return lua_error(L); +} + +int lcurl_fail(lua_State *L, int error_type, int code){ + return lcurl_fail_ex(L, LCURL_ERROR_RETURN, error_type, code); +} + +//} + +static const int ERROR_CATEGORIES[] = { + LCURL_ERROR_EASY, + LCURL_ERROR_MULTI, + LCURL_ERROR_SHARE, + LCURL_ERROR_FORM, + LCURL_ERROR_URL, +}; + +static const char* ERROR_CATEGORIES_NAME[] = { + LCURL_ERROR_EASY_NAME, + LCURL_ERROR_MULTI_NAME, + LCURL_ERROR_SHARE_NAME, + LCURL_ERROR_FORM_NAME, + LCURL_ERROR_URL_NAME, + NULL +}; + +int lcurl_error_new(lua_State *L){ + int tp, no = luaL_checkint(L, 2); + if (lua_isnumber(L, 1)){ + tp = luaL_checkint(L, 2); + } + else{ + tp = luaL_checkoption(L, 1, NULL, ERROR_CATEGORIES_NAME); + tp = ERROR_CATEGORIES[tp]; + } + + //! @todo checks error type value + + lcurl_error_create(L, tp, no); + return 1; +} + +static const struct luaL_Reg lcurl_err_methods[] = { + {"no", lcurl_err_no }, + {"msg", lcurl_err_msg }, + {"name", lcurl_err_mnemo }, + {"mnemo", lcurl_err_mnemo }, + {"cat", lcurl_err_category }, + {"category", lcurl_err_category }, + {"__tostring", lcurl_err_tostring }, + {"__eq", lcurl_err_equal }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_error_codes[] = { + +#define ERR_ENTRY(N) { "E_"#N, CURLE_##N }, +#include "lcerr_easy.h" +#undef ERR_ENTRY + +/* libcurl rename CURLE_FTP_WEIRD_SERVER_REPLY to CURLE_WEIRD_SERVER_REPLY in version 7.51.0*/ +/* we can not have both codes in general because we have to be able convern error number to error name*/ +/* so we use newest name but add error code as alias.*/ +#if LCURL_CURL_VER_GE(7,51,0) + { "E_FTP_WEIRD_SERVER_REPLY", CURLE_FTP_WEIRD_SERVER_REPLY }, +#else + { "E_WEIRD_SERVER_REPLY", CURLE_FTP_WEIRD_SERVER_REPLY }, +#endif + +#define ERR_ENTRY(N) { "E_MULTI_"#N, CURLM_##N }, +#include "lcerr_multi.h" +#undef ERR_ENTRY + +#define ERR_ENTRY(N) { "E_SHARE_"#N, CURLSHE_##N }, +#include "lcerr_share.h" +#undef ERR_ENTRY + +#define ERR_ENTRY(N) { "E_FORM_"#N, CURL_FORMADD_##N }, +#include "lcerr_form.h" +#undef ERR_ENTRY + +#define ERR_ENTRY(N) { "E_URL_"#N, CURLUE_##N }, +#include "lcerr_url.h" +#undef ERR_ENTRY + + {NULL, 0} +}; + +void lcurl_error_initlib(lua_State *L, int nup){ + if(!lutil_createmetap(L, LCURL_ERROR, lcurl_err_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_error_codes); + + lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_EASY ));lua_setfield(L, -2, "ERROR_EASY" ); + lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_MULTI ));lua_setfield(L, -2, "ERROR_MULTI"); + lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_SHARE ));lua_setfield(L, -2, "ERROR_SHARE"); + lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_FORM ));lua_setfield(L, -2, "ERROR_FORM" ); +} diff --git a/watchdog/third_party/lua-curl/src/lcerror.h b/watchdog/third_party/lua-curl/src/lcerror.h new file mode 100644 index 0000000..da4e1c5 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerror.h @@ -0,0 +1,34 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCERROR_H_ +#define _LCERROR_H_ + +#include "lcurl.h" + +#define LCURL_ERROR_CURL 1 +#define LCURL_ERROR_EASY 1 +#define LCURL_ERROR_MULTI 2 +#define LCURL_ERROR_SHARE 3 +#define LCURL_ERROR_FORM 4 +#define LCURL_ERROR_URL 5 + +#define LCURL_ERROR_RETURN 1 +#define LCURL_ERROR_RAISE 2 + +int lcurl_fail(lua_State *L, int error_type, int code); + +int lcurl_fail_ex(lua_State *L, int mode, int error_type, int code); + +int lcurl_error_new(lua_State *L); + +void lcurl_error_initlib(lua_State *L, int nup); + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcflags.h b/watchdog/third_party/lua-curl/src/lcflags.h new file mode 100644 index 0000000..0fbeff2 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcflags.h @@ -0,0 +1,283 @@ +/* Bitmasks for CURLOPT_HTTPAUTH and CURLOPT_PROXYAUTH options */ +FLG_ENTRY(AUTH_NONE ) +FLG_ENTRY(AUTH_BASIC ) +FLG_ENTRY(AUTH_DIGEST ) +FLG_ENTRY(AUTH_GSSNEGOTIATE ) +#if LCURL_CURL_VER_GE(7,38,0) +FLG_ENTRY(AUTH_NEGOTIATE ) +#endif +FLG_ENTRY(AUTH_NTLM ) +#if LCURL_CURL_VER_GE(7,19,3) +FLG_ENTRY(AUTH_DIGEST_IE ) +#endif +#if LCURL_CURL_VER_GE(7,19,6) +FLG_ENTRY(KHSTAT_FINE_ADD_TO_FILE ) +FLG_ENTRY(KHSTAT_FINE ) +FLG_ENTRY(KHSTAT_REJECT ) +FLG_ENTRY(KHSTAT_DEFER ) +FLG_ENTRY(KHMATCH_OK ) +FLG_ENTRY(KHMATCH_MISMATCH ) +FLG_ENTRY(KHMATCH_MISSING ) +FLG_ENTRY(KHTYPE_RSA1 ) +FLG_ENTRY(KHTYPE_RSA ) +FLG_ENTRY(KHTYPE_DSS ) +#endif +#if LCURL_CURL_VER_GE(7,58,0) +FLG_ENTRY(KHTYPE_ECDSA ) +FLG_ENTRY(KHTYPE_ED25519 ) +#endif +#if LCURL_CURL_VER_GE(7,73,0) +FLG_ENTRY(KHSTAT_FINE_REPLACE ) +#endif + +#if LCURL_CURL_VER_GE(7,22,0) +FLG_ENTRY(AUTH_NTLM_WB ) +#endif +#if LCURL_CURL_VER_GE(7,21,3) +FLG_ENTRY(AUTH_ONLY ) +#endif +FLG_ENTRY(AUTH_ANY ) +FLG_ENTRY(AUTH_ANYSAFE ) +#if LCURL_CURL_VER_GE(7,55,0) +FLG_ENTRY(AUTH_GSSAPI ) +#endif +#if LCURL_CURL_VER_GE(7,61,0) +FLG_ENTRY(AUTH_BEARER ) +#endif + +#ifdef CURLSSH_AUTH_ANY +FLG_ENTRY(SSH_AUTH_ANY ) +#endif +#ifdef CURLSSH_AUTH_NONE +FLG_ENTRY(SSH_AUTH_NONE ) +#endif +#ifdef CURLSSH_AUTH_PUBLICKEY +FLG_ENTRY(SSH_AUTH_PUBLICKEY ) +#endif +#ifdef CURLSSH_AUTH_PASSWORD +FLG_ENTRY(SSH_AUTH_PASSWORD ) +#endif +#ifdef CURLSSH_AUTH_HOST +FLG_ENTRY(SSH_AUTH_HOST ) +#endif +#ifdef CURLSSH_AUTH_GSSAPI +FLG_ENTRY(SSH_AUTH_GSSAPI ) +#endif +#ifdef CURLSSH_AUTH_KEYBOARD +FLG_ENTRY(SSH_AUTH_KEYBOARD ) +#endif +#ifdef CURLSSH_AUTH_AGENT +FLG_ENTRY(SSH_AUTH_AGENT ) +#endif +#ifdef CURLSSH_AUTH_DEFAULT +FLG_ENTRY(SSH_AUTH_DEFAULT ) +#endif + +#ifdef CURLGSSAPI_DELEGATION_NONE +FLG_ENTRY(GSSAPI_DELEGATION_NONE ) +#endif +#ifdef CURLGSSAPI_DELEGATION_POLICY_FLAG +FLG_ENTRY(GSSAPI_DELEGATION_POLICY_FLAG ) +#endif +#ifdef CURLGSSAPI_DELEGATION_FLAG +FLG_ENTRY(GSSAPI_DELEGATION_FLAG ) +#endif + +/* Bitmasks for CURLOPT_HTTPAUTH and CURLOPT_PROXYAUTH options */ +FLG_ENTRY(USESSL_NONE ) +FLG_ENTRY(USESSL_TRY ) +FLG_ENTRY(USESSL_CONTROL ) +FLG_ENTRY(USESSL_ALL ) + +/* Definition of bits for the CURLOPT_SSL_OPTIONS argument: */ +#ifdef CURLSSLOPT_ALLOW_BEAST +FLG_ENTRY(SSLOPT_ALLOW_BEAST ) +#endif +#ifdef CURLSSLOPT_NO_REVOKE +FLG_ENTRY(SSLOPT_NO_REVOKE ) +#endif +#ifdef CURLSSLOPT_NO_PARTIALCHAIN +FLG_ENTRY(SSLOPT_NO_PARTIALCHAIN ) +#endif +#ifdef CURLSSLOPT_REVOKE_BEST_EFFORT +FLG_ENTRY(SSLOPT_REVOKE_BEST_EFFORT ) +#endif +#ifdef CURLSSLOPT_NATIVE_CA +FLG_ENTRY(SSLOPT_NATIVE_CA ) +#endif + +/* parameter for the CURLOPT_FTP_SSL_CCC option */ +FLG_ENTRY(FTPSSL_CCC_NONE ) +FLG_ENTRY(FTPSSL_CCC_PASSIVE ) +FLG_ENTRY(FTPSSL_CCC_ACTIVE ) + +/* parameter for the CURLOPT_FTPSSLAUTH option */ +FLG_ENTRY(FTPAUTH_DEFAULT ) +FLG_ENTRY(FTPAUTH_SSL ) +FLG_ENTRY(FTPAUTH_TLS ) + +/* parameter for the CURLOPT_FTP_CREATE_MISSING_DIRS option */ +FLG_ENTRY(FTP_CREATE_DIR_NONE ) +FLG_ENTRY(FTP_CREATE_DIR ) +FLG_ENTRY(FTP_CREATE_DIR_RETRY ) +FLG_ENTRY(FTP_CREATE_DIR_LAST ) + +/* parameter for the CURLOPT_FTP_FILEMETHOD option */ +FLG_ENTRY(FTPMETHOD_DEFAULT ) +FLG_ENTRY(FTPMETHOD_MULTICWD ) +FLG_ENTRY(FTPMETHOD_NOCWD ) +FLG_ENTRY(FTPMETHOD_SINGLECWD ) + +/* bitmask defines for CURLOPT_HEADEROPT */ +#if LCURL_CURL_VER_GE(7,37,0) +FLG_ENTRY(HEADER_UNIFIED ) +FLG_ENTRY(HEADER_SEPARATE ) +#endif + +/* CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */ +FLG_ENTRY(PROTO_HTTP ) +FLG_ENTRY(PROTO_HTTPS ) +FLG_ENTRY(PROTO_FTP ) +FLG_ENTRY(PROTO_FTPS ) +FLG_ENTRY(PROTO_SCP ) +FLG_ENTRY(PROTO_SFTP ) +FLG_ENTRY(PROTO_TELNET ) +FLG_ENTRY(PROTO_LDAP ) +FLG_ENTRY(PROTO_LDAPS ) +FLG_ENTRY(PROTO_DICT ) +FLG_ENTRY(PROTO_FILE ) +FLG_ENTRY(PROTO_TFTP ) +#ifdef CURLPROTO_IMAP +FLG_ENTRY(PROTO_IMAP ) +#endif +#ifdef CURLPROTO_IMAPS +FLG_ENTRY(PROTO_IMAPS ) +#endif +#ifdef CURLPROTO_POP3 +FLG_ENTRY(PROTO_POP3 ) +#endif +#ifdef CURLPROTO_POP3S +FLG_ENTRY(PROTO_POP3S ) +#endif +#ifdef CURLPROTO_SMTP +FLG_ENTRY(PROTO_SMTP ) +#endif +#ifdef CURLPROTO_SMTPS +FLG_ENTRY(PROTO_SMTPS ) +#endif +#ifdef CURLPROTO_RTSP +FLG_ENTRY(PROTO_RTSP ) +#endif +#ifdef CURLPROTO_RTMP +FLG_ENTRY(PROTO_RTMP ) +#endif +#ifdef CURLPROTO_RTMPT +FLG_ENTRY(PROTO_RTMPT ) +#endif +#ifdef CURLPROTO_RTMPE +FLG_ENTRY(PROTO_RTMPE ) +#endif +#ifdef CURLPROTO_RTMPTE +FLG_ENTRY(PROTO_RTMPTE ) +#endif +#ifdef CURLPROTO_RTMPS +FLG_ENTRY(PROTO_RTMPS ) +#endif +#ifdef CURLPROTO_RTMPTS +FLG_ENTRY(PROTO_RTMPTS ) +#endif +#ifdef CURLPROTO_GOPHER +FLG_ENTRY(PROTO_GOPHER ) +#endif +#ifdef CURLPROTO_SMB +FLG_ENTRY(PROTO_SMB ) +#endif +#ifdef CURLPROTO_SMBS +FLG_ENTRY(PROTO_SMBS ) +#endif +#ifdef CURLPROTO_MQTT +FLG_ENTRY(PROTO_MQTT ) +#endif +FLG_ENTRY(PROTO_ALL ) + +FLG_ENTRY(PROXY_HTTP ) /* added in 7.10.0 */ +FLG_ENTRY(PROXY_HTTP_1_0 ) /* added in 7.19.4 */ +FLG_ENTRY(PROXY_SOCKS4 ) /* added in 7.15.2 */ +FLG_ENTRY(PROXY_SOCKS5 ) /* added in 7.10.0 */ +FLG_ENTRY(PROXY_SOCKS4A ) /* added in 7.18.0 */ +FLG_ENTRY(PROXY_SOCKS5_HOSTNAME ) /* added in 7.18.0 */ +#if LCURL_CURL_VER_GE(7,52,0) +FLG_ENTRY(PROXY_HTTPS ) +#endif + +FLG_ENTRY(PAUSE_ALL ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_CONT ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_RECV ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_RECV_CONT ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_SEND ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_SEND_CONT ) /* added in 7.18.0 */ + +#if LCURL_CURL_VER_GE(7,64,1) +FLG_ENTRY(ALTSVC_H1) +FLG_ENTRY(ALTSVC_H2) +FLG_ENTRY(ALTSVC_H3) +FLG_ENTRY(ALTSVC_READONLYFILE) +#endif + +#if LCURL_CURL_VER_GE(7,73,0) +FLG_ENTRY(PX_OK) +FLG_ENTRY(PX_BAD_ADDRESS_TYPE) +FLG_ENTRY(PX_BAD_VERSION) +FLG_ENTRY(PX_CLOSED) +FLG_ENTRY(PX_GSSAPI) +FLG_ENTRY(PX_GSSAPI_PERMSG) +FLG_ENTRY(PX_GSSAPI_PROTECTION) +FLG_ENTRY(PX_IDENTD) +FLG_ENTRY(PX_IDENTD_DIFFER) +FLG_ENTRY(PX_LONG_HOSTNAME) +FLG_ENTRY(PX_LONG_PASSWD) +FLG_ENTRY(PX_LONG_USER) +FLG_ENTRY(PX_NO_AUTH) +FLG_ENTRY(PX_RECV_ADDRESS) +FLG_ENTRY(PX_RECV_AUTH) +FLG_ENTRY(PX_RECV_CONNECT) +FLG_ENTRY(PX_RECV_REQACK) +FLG_ENTRY(PX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED) +FLG_ENTRY(PX_REPLY_COMMAND_NOT_SUPPORTED) +FLG_ENTRY(PX_REPLY_CONNECTION_REFUSED) +FLG_ENTRY(PX_REPLY_GENERAL_SERVER_FAILURE) +FLG_ENTRY(PX_REPLY_HOST_UNREACHABLE) +FLG_ENTRY(PX_REPLY_NETWORK_UNREACHABLE) +FLG_ENTRY(PX_REPLY_NOT_ALLOWED) +FLG_ENTRY(PX_REPLY_TTL_EXPIRED) +FLG_ENTRY(PX_REPLY_UNASSIGNED) +FLG_ENTRY(PX_REQUEST_FAILED) +FLG_ENTRY(PX_RESOLVE_HOST) +FLG_ENTRY(PX_SEND_AUTH) +FLG_ENTRY(PX_SEND_CONNECT) +FLG_ENTRY(PX_SEND_REQUEST) +FLG_ENTRY(PX_UNKNOWN_FAIL) +FLG_ENTRY(PX_UNKNOWN_MODE) +FLG_ENTRY(PX_USER_REJECTED) +#endif + +#if LCURL_CURL_VER_GE(7,73,0) +FLG_ENTRY(OT_LONG) +FLG_ENTRY(OT_VALUES) +FLG_ENTRY(OT_OFF_T) +FLG_ENTRY(OT_OBJECT) +FLG_ENTRY(OT_STRING) +FLG_ENTRY(OT_SLIST) +FLG_ENTRY(OT_CBPTR) +FLG_ENTRY(OT_BLOB) +FLG_ENTRY(OT_FUNCTION) +FLG_ENTRY(OT_FLAG_ALIAS) +#endif + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS +FLG_ENTRY(HSTS_ENABLE) +FLG_ENTRY(HSTS_READONLYFILE) +FLG_ENTRY(STS_OK) +FLG_ENTRY(STS_DONE) +FLG_ENTRY(STS_FAIL) +#endif diff --git a/watchdog/third_party/lua-curl/src/lchttppost.c b/watchdog/third_party/lua-curl/src/lchttppost.c new file mode 100644 index 0000000..2d49323 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lchttppost.c @@ -0,0 +1,595 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lchttppost.h" +#include "lcerror.h" +#include "lcutils.h" + +#define LCURL_HTTPPOST_NAME LCURL_PREFIX" HTTPPost" +static const char *LCURL_HTTPPOST = LCURL_HTTPPOST_NAME; + + +#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ + +/*! @fixme detect real types (e.g. float/int32_t) */ + +# define LCURL_USE_INTEGER + +#endif + +#ifdef LCURL_USE_INTEGER +# ifdef LUA_32BITS +# define LCURL_INT_SIZE_16 +# define LCURL_INT_SIZE_32 +# else +# define LCURL_INT_SIZE_16 +# define LCURL_INT_SIZE_32 +# define LCURL_INT_SIZE_64 +# endif +#endif + +#if LCURL_CURL_VER_GE(7,46,0) +# define LCURL_FORM_CONTENTLEN CURLFORM_CONTENTLEN +# define LCURL_LEN_TYPE curl_off_t +#else +# define LCURL_FORM_CONTENTLEN CURLFORM_CONTENTSLENGTH +# define LCURL_LEN_TYPE long +#endif + +/* 7.56.0 changed code for `curl_formget` if callback abort write. + * + * https://github.com/curl/curl/issues/1987#issuecomment-336139060 + * ... not sure its worth the effort to document its return codes to + * any further extent then it currently is. This function is very + * rarely used, and the new mime API doesn't even have a version of it. + **/ +#if LCURL_CURL_VER_GE(7,56,0) +# define LCURL_GET_CB_ERROR CURLE_READ_ERROR +#else +# define LCURL_GET_CB_ERROR (CURLcode)-1 +#endif + +//{ stream + +static lcurl_hpost_stream_t *lcurl_hpost_stream_add(lua_State *L, lcurl_hpost_t *p){ + lcurl_hpost_stream_t *ptr = p->stream; + lcurl_hpost_stream_t *stream = malloc(sizeof(lcurl_hpost_stream_t)); + if(!stream) return NULL; + + stream->magic = LCURL_HPOST_STREAM_MAGIC; + stream->L = &p->L; + stream->rbuffer.ref = LUA_NOREF; + stream->rd.cb_ref = stream->rd.ud_ref = LUA_NOREF; + stream->next = NULL; + if(!p->stream) p->stream = stream; + else{ + while(ptr->next) ptr = ptr->next; + ptr->next = stream; + } + return stream; +} + +static void lcurl_hpost_stream_free(lua_State *L, lcurl_hpost_stream_t *ptr){ + if(ptr){ + luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rbuffer.ref); + luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rd.ud_ref); + free(ptr); + } +} + +static void lcurl_hpost_stream_free_last(lua_State *L, lcurl_hpost_t *p){ + lcurl_hpost_stream_t *ptr = p->stream; + if(!ptr) return; + if(!ptr->next){ + lcurl_hpost_stream_free(L, ptr); + p->stream = 0; + } + + while(ptr->next->next) ptr = ptr->next; + lcurl_hpost_stream_free(L, ptr->next); + ptr->next = NULL; +} + +static void lcurl_hpost_stream_free_all(lua_State *L, lcurl_hpost_t *p){ + lcurl_hpost_stream_t *ptr = p->stream; + while(ptr){ + lcurl_hpost_stream_t *next = ptr->next; + lcurl_hpost_stream_free(L, ptr); + ptr = next; + } + p->stream = 0; +} + +//} + +//{ HTTPPost + +int lcurl_hpost_create(lua_State *L, int error_mode){ + lcurl_hpost_t *p = lutil_newudatap(L, lcurl_hpost_t, LCURL_HTTPPOST); + p->post = p->last = 0; + p->storage = lcurl_storage_init(L); + p->err_mode = error_mode; + p->stream = 0; + + return 1; +} + +lcurl_hpost_t *lcurl_gethpost_at(lua_State *L, int i){ + lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, i, LCURL_HTTPPOST); + luaL_argcheck (L, p != NULL, 1, LCURL_HTTPPOST_NAME" object expected"); + return p; +} + +static int lcurl_hpost_to_s(lua_State *L){ + lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, 1, LCURL_HTTPPOST); + lua_pushfstring(L, LCURL_HTTPPOST_NAME" (%p)", (void*)p); + return 1; +} + +static int lcurl_hpost_add_content(lua_State *L){ + // add_buffer(name, data, [type,] [headers]) + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + size_t cont_len; const char *cont = luaL_checklstring(L, 3, &cont_len); + const char *type = lua_tostring(L, 4); + struct curl_slist *list = lcurl_util_to_slist(L, type?5:4); + struct curl_forms forms[3]; + CURLFORMcode code; + + int i = 0; + if(type){ forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; } + if(list){ forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; } + forms[i].option = CURLFORM_END; + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_PTRCONTENTS, cont, LCURL_FORM_CONTENTLEN, (LCURL_LEN_TYPE)cont_len, + CURLFORM_ARRAY, forms, + CURLFORM_END); + + if(code != CURL_FORMADD_OK){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lcurl_storage_preserve_value(L, p->storage, 2); + lcurl_storage_preserve_value(L, p->storage, 3); + if(list) lcurl_storage_preserve_slist (L, p->storage, list); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_add_buffer(lua_State *L){ + // add_buffer(name, filename, data, [type,] [headers]) + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + const char *buff = luaL_checkstring(L, 3); + size_t cont_len; const char *cont = luaL_checklstring(L, 4, &cont_len); + const char *type = lua_tostring(L, 5); + struct curl_slist *list = lcurl_util_to_slist(L, ((!type)&&(lua_isnone(L,6)))?5:6); + struct curl_forms forms[3]; + CURLFORMcode code; + + int i = 0; + if(type){ forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; } + if(list){ forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; } + forms[i].option = CURLFORM_END; + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_BUFFER, buff, + CURLFORM_BUFFERPTR, cont, CURLFORM_BUFFERLENGTH, cont_len, + CURLFORM_ARRAY, forms, + CURLFORM_END); + + if(code != CURL_FORMADD_OK){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lcurl_storage_preserve_value(L, p->storage, 2); + lcurl_storage_preserve_value(L, p->storage, 4); + if(list) lcurl_storage_preserve_slist (L, p->storage, list); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_add_file(lua_State *L){ + // add_file(name, path, [type, [fname,]] [headers]) + // add_file("Picture", "c:\\image.jpg") + // add_file("Picture", "c:\\image.jpg", "image/jpeg") + // add_file("Picture", "c:\\image.jpg", "image/jpeg", {"XDescript: my image"}) + // add_file("Picture", "c:\\image.jpg", "image/jpeg", "avatar.jpeg", {"XDescript: my image"}) + // add_file("Picture", "c:\\image.jpg", nil, "avatar.jpeg", {"XDescript: my image"}) + + int top = lua_gettop(L); + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + const char *path = luaL_checkstring(L, 3); + const char *type = 0, *fname = 0; + struct curl_slist *list = NULL; + struct curl_forms forms[4]; + CURLFORMcode code; + int i = 0; + + if(top == 4){ /* name, path, type | headers */ + if(lua_istable(L, 4)) + list = lcurl_util_to_slist(L, 4); + else + type = lua_tostring(L, 4); + } + else if(top > 4){ /* name, path, type, fname | [fname, headers] */ + type = lua_tostring(L, 4); + if(top == 5){ /* name, path, type, fname | headers */ + if(lua_istable(L, 5)) + list = lcurl_util_to_slist(L, 5); + else + fname = lua_tostring(L, 5); + } + else{ /* name, path, type, fname, headers */ + fname = lua_tostring(L, 5); + list = lcurl_util_to_slist(L, 6); + } + } + + if(fname){ forms[i].option = CURLFORM_FILENAME; forms[i++].value = fname; } + if(type) { forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; } + if(list) { forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; } + forms[i].option = CURLFORM_END; + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_FILE, path, + CURLFORM_ARRAY, forms, + CURLFORM_END); + + if(code != CURL_FORMADD_OK){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lcurl_storage_preserve_value(L, p->storage, 2); + if(list) lcurl_storage_preserve_slist (L, p->storage, list); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_add_stream(lua_State *L){ + static const char *EMPTY = ""; + + // add_stream(name, [filename, [type,]] [headers,] size, reader [,context]) + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + struct curl_slist *list = NULL; int ilist = 0; + const char *type = 0, *fname = 0; + size_t len; + CURLFORMcode code; + lcurl_callback_t rd = {LUA_NOREF, LUA_NOREF}; + lcurl_hpost_stream_t *stream; + int n = 0, i = 3; + struct curl_forms forms[4]; + + while(1){ // [filename, [type,]] [headers,] + if(lua_isnone(L, i)){ + lua_pushliteral(L, "stream size required"); + lua_error(L); + } + if(lua_type(L, i) == LUA_TNUMBER){ + break; + } + if(lua_type(L, i) == LUA_TTABLE){ + ilist = i++; + break; + } + else if(!fname){ + if(lua_isnil(L, i)) fname = EMPTY; + else fname = luaL_checkstring(L, i); + } + else if(!type){ + if(lua_isnil(L, i)) type = EMPTY; + else type = luaL_checkstring(L, i); + } + else{ + if(lua_isnil(L, i) && (!ilist)){ + ++i; // empty headers + break; + } + lua_pushliteral(L, "stream size required"); + lua_error(L); + } + ++i; + } + +#if defined(LCURL_INT_SIZE_64) && LCURL_CURL_VER_GE(7,46,0) + len = luaL_checkinteger(L, i); +#else + len = luaL_checklong(L, i); +#endif + + lcurl_set_callback(L, &rd, i + 1, "read"); + + luaL_argcheck(L, rd.cb_ref != LUA_NOREF, i + 1, "function expected"); + + if(ilist) list = lcurl_util_to_slist(L, ilist); + if(fname == EMPTY) fname = NULL; + if(type == EMPTY) type = NULL; + + n = 0; + if(fname){ forms[n].option = CURLFORM_FILENAME; forms[n++].value = fname; } + if(type) { forms[n].option = CURLFORM_CONTENTTYPE; forms[n++].value = type; } + if(list) { forms[n].option = CURLFORM_CONTENTHEADER; forms[n++].value = (char*)list; } + forms[n].option = CURLFORM_END; + + stream = lcurl_hpost_stream_add(L, p); + if(!stream){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_MEMORY); + } + + stream->rd = rd; + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_STREAM, stream, LCURL_FORM_CONTENTLEN, (LCURL_LEN_TYPE)len, + CURLFORM_ARRAY, forms, + CURLFORM_END + ); + + if(code != CURL_FORMADD_OK){ + lcurl_hpost_stream_free_last(L, p); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lcurl_storage_preserve_value(L, p->storage, 2); + if(list) lcurl_storage_preserve_slist (L, p->storage, list); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_add_files(lua_State *L){ + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + int i; int opt_count = 0; + int arr_count = lua_rawlen(L, 3); + struct curl_forms *forms; + CURLFORMcode code; + + lua_settop(L, 3); + if(lua_type(L, -1) != LUA_TTABLE){ + //! @fixme use library specific error codes + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY); + } + + for(i = 1; i <= arr_count; ++i){ + int n; + lua_rawgeti(L, 3, i); + + if((lua_type(L, -1) != LUA_TTABLE) && (lua_type(L, -1) != LUA_TSTRING)){ + //! @fixme use library specific error codes + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY); + } + + n = (lua_type(L, -1) == LUA_TSTRING) ? 1: lua_rawlen(L, -1); + if(n == 1) opt_count += 1; // name + else if(n == 2) opt_count += 2; // name and type + else if(n == 3) opt_count += 3; // name, type and filename + else{ + //! @fixme use library specific error codes + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY); + } + + lua_pop(L, 1); + } + + if(opt_count == 0){ + lua_settop(L, 1); + return 1; + } + + forms = calloc(opt_count + 1, sizeof(struct curl_forms)); + if(!forms){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_MEMORY); + } + forms[opt_count].option = CURLFORM_END; + + opt_count = 0; + for(i = 1; i <= arr_count; ++i){ + int n; + + lua_rawgeti(L, 3, i); + if (lua_type(L, -1) == LUA_TSTRING){ + forms[opt_count].option = CURLFORM_FILE; forms[opt_count++].value = luaL_checkstring(L, -1); + } + else{ + n = lua_rawlen(L, -1); + lua_rawgeti(L, -1, 1); + forms[opt_count].option = CURLFORM_FILE; forms[opt_count++].value = luaL_checkstring(L, -1); + lua_pop(L, 1); + if(n > 1){ + lua_rawgeti(L, -1, 2); + forms[opt_count].option = CURLFORM_CONTENTTYPE; forms[opt_count++].value = luaL_checkstring(L, -1); + lua_pop(L, 1); + } + if(n > 2){ + lua_rawgeti(L, -1, 3); + forms[opt_count].option = CURLFORM_FILENAME; forms[opt_count++].value = luaL_checkstring(L, -1); + lua_pop(L, 1); + } + } + + lua_pop(L, 1); + } + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_ARRAY, forms, + CURLFORM_END); + + free(forms); + + if(code != CURL_FORMADD_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lua_settop(L, 1); + return 1; +} + +static size_t lcurl_hpost_getter_by_buffer(void *arg, const char *buf, size_t len){ + luaL_Buffer *b = arg; + luaL_addlstring(b, buf, len); + return len; +} + +static size_t call_writer(lua_State *L, int fn, int ctx, const char *buf, size_t len){ + int top = lua_gettop(L); + int n = 1; // number of args + lua_Number ret = (lua_Number)len; + + lua_pushvalue(L, fn); + if(ctx){ + lua_pushvalue(L, ctx); + n += 1; + } + lua_pushlstring(L, buf, len); + + if(lua_pcall(L, n, LUA_MULTRET, 0)) return 0; + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1)) return 0; + if(lua_isboolean(L, top + 1)){ + if(!lua_toboolean(L, top + 1)) ret = 0; + } + else ret = lua_tonumber(L, top + 1); + } + lua_settop(L, top); + + return (size_t)ret; +} + +static size_t lcurl_hpost_getter_by_callback1(void *arg, const char *buf, size_t len){ + lua_State *L = arg; + assert(2 == lua_gettop(L)); + return call_writer(L, 2, 0, buf, len); +} + +static size_t lcurl_hpost_getter_by_callback2(void *arg, const char *buf, size_t len){ + lua_State *L = arg; + assert(3 == lua_gettop(L)); + return call_writer(L, 2, 3, buf, len); +} + +static int lcurl_hpost_get(lua_State *L){ + // get() + // get(fn [, ctx]) + // get(object) + lcurl_hpost_t *p = lcurl_gethpost(L); + CURLcode code; + int top; + + if(lua_isnoneornil(L, 2)){ + luaL_Buffer b; + luaL_buffinit(L, &b); + + code = curl_formget(p->post, &b, lcurl_hpost_getter_by_buffer); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_CURL, code); + } + + luaL_pushresult(&b); + return 1; + } + + if(lua_isfunction(L, 2)){ + if(lua_gettop(L) == 2){ + top = 2; + code = curl_formget(p->post, L, lcurl_hpost_getter_by_callback1); + } + else{ + top = 3; + lua_settop(L, 3); + code = curl_formget(p->post, L, lcurl_hpost_getter_by_callback2); + } + } + else if(lua_isuserdata(L, 2) || lua_istable(L, 2)){ + lua_settop(L, 2); + lua_getfield(L, 2, "write"); + luaL_argcheck(L, lua_isfunction(L, -1), 2, "write method not found in object"); + assert(3 == lua_gettop(L)); + lua_insert(L, -2); + top = 3; + code = curl_formget(p->post, L, lcurl_hpost_getter_by_callback2); + } + else{ + lua_pushliteral(L, "invalid writer type"); + return lua_error(L); + } + + if(LCURL_GET_CB_ERROR == code){ + if(((lua_gettop(L) == top+1))&&(lua_isstring(L, -1))){ + return lua_error(L); + } + return lua_gettop(L) - top; + } + + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_CURL, code); + } + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_free(lua_State *L){ + lcurl_hpost_t *p = lcurl_gethpost(L); + if(p->post){ + curl_formfree(p->post); + p->post = p->last = 0; + } + + if(p->storage != LUA_NOREF){ + p->storage = lcurl_storage_free(L, p->storage); + } + + lcurl_hpost_stream_free_all(L, p); + + return 0; +} + +//} + +static const struct luaL_Reg lcurl_hpost_methods[] = { + {"add_content", lcurl_hpost_add_content }, + {"add_buffer", lcurl_hpost_add_buffer }, + {"add_file", lcurl_hpost_add_file }, + {"add_stream", lcurl_hpost_add_stream }, + + {"add_files", lcurl_hpost_add_files }, + + {"get", lcurl_hpost_get }, + {"free", lcurl_hpost_free }, + {"__gc", lcurl_hpost_free }, + {"__tostring", lcurl_hpost_to_s }, + + {NULL,NULL} +}; + +void lcurl_hpost_initlib(lua_State *L, int nup){ + if(!lutil_createmetap(L, LCURL_HTTPPOST, lcurl_hpost_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); +} + diff --git a/watchdog/third_party/lua-curl/src/lchttppost.h b/watchdog/third_party/lua-curl/src/lchttppost.h new file mode 100644 index 0000000..3299069 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lchttppost.h @@ -0,0 +1,47 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCHTTPPOST_H_ +#define _LCHTTPPOST_H_ + +#include "lcurl.h" +#include "lcutils.h" +#include + +#define LCURL_HPOST_STREAM_MAGIC 0xAA + +typedef struct lcurl_hpost_stream_tag{ + unsigned char magic; + + lua_State **L; + lcurl_callback_t rd; + lcurl_read_buffer_t rbuffer; + struct lcurl_hpost_stream_tag *next; +}lcurl_hpost_stream_t; + +typedef struct lcurl_hpost_tag{ + lua_State *L; + struct curl_httppost *post; + struct curl_httppost *last; + int storage; + int err_mode; + lcurl_hpost_stream_t *stream; +}lcurl_hpost_t; + +int lcurl_hpost_create(lua_State *L, int error_mode); + +void lcurl_hpost_initlib(lua_State *L, int nup); + +lcurl_hpost_t *lcurl_gethpost_at(lua_State *L, int i); + +#define lcurl_gethpost(L) lcurl_gethpost_at((L),1) + + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcinfoeasy.h b/watchdog/third_party/lua-curl/src/lcinfoeasy.h new file mode 100644 index 0000000..6cbf8a9 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcinfoeasy.h @@ -0,0 +1,94 @@ +OPT_ENTRY( effective_url, EFFECTIVE_URL, STR, 0) +OPT_ENTRY( response_code, RESPONSE_CODE, LNG, 0) +OPT_ENTRY( http_connectcode, HTTP_CONNECTCODE, LNG, 0) +OPT_ENTRY( filetime, FILETIME, LNG, 0) +OPT_ENTRY( total_time, TOTAL_TIME, DBL, 0) +OPT_ENTRY( namelookup_time, NAMELOOKUP_TIME, DBL, 0) +OPT_ENTRY( connect_time, CONNECT_TIME, DBL, 0) +OPT_ENTRY( appconnect_time, APPCONNECT_TIME, DBL, 0) +OPT_ENTRY( pretransfer_time, PRETRANSFER_TIME, DBL, 0) +OPT_ENTRY( starttransfer_time, STARTTRANSFER_TIME, DBL, 0) +OPT_ENTRY( redirect_time, REDIRECT_TIME, DBL, 0) +OPT_ENTRY( redirect_count, REDIRECT_COUNT, LNG, 0) +OPT_ENTRY( redirect_url, REDIRECT_URL, STR, 0) +OPT_ENTRY( size_upload, SIZE_UPLOAD, DBL, 0) +OPT_ENTRY( size_download, SIZE_DOWNLOAD, DBL, 0) +OPT_ENTRY( speed_download, SPEED_DOWNLOAD, DBL, 0) +OPT_ENTRY( speed_upload, SPEED_UPLOAD, DBL, 0) +OPT_ENTRY( header_size, HEADER_SIZE, LNG, 0) +OPT_ENTRY( request_size, REQUEST_SIZE, LNG, 0) +OPT_ENTRY( ssl_verifyresult, SSL_VERIFYRESULT, LNG, 0) +OPT_ENTRY( ssl_engines, SSL_ENGINES, LST, 0) +OPT_ENTRY( content_length_download, CONTENT_LENGTH_DOWNLOAD, DBL, 0) +OPT_ENTRY( content_length_upload, CONTENT_LENGTH_UPLOAD, DBL, 0) +OPT_ENTRY( content_type, CONTENT_TYPE, STR, 0) +OPT_ENTRY( httpauth_avail, HTTPAUTH_AVAIL, LNG, 0) +OPT_ENTRY( proxyauth_avail, PROXYAUTH_AVAIL, LNG, 0) +OPT_ENTRY( os_errno, OS_ERRNO, LNG, 0) +OPT_ENTRY( num_connects, NUM_CONNECTS, LNG, 0) +OPT_ENTRY( primary_ip, PRIMARY_IP, STR, 0) +OPT_ENTRY( certinfo, CERTINFO, CERTINFO, 0) +#if LCURL_CURL_VER_GE(7,21,0) +OPT_ENTRY( primary_port, PRIMARY_PORT, LNG, 0) +OPT_ENTRY( local_ip, LOCAL_IP, STR, 0) +OPT_ENTRY( local_port, LOCAL_PORT, LNG, 0) +#endif +OPT_ENTRY( cookielist, COOKIELIST, LST, 0) +OPT_ENTRY( lastsocket, LASTSOCKET, LNG, 0) +OPT_ENTRY( ftp_entry_path, FTP_ENTRY_PATH, STR, 0) +OPT_ENTRY( condition_unmet, CONDITION_UNMET, LNG, 0) +#if LCURL_CURL_VER_GE(7,20,0) +OPT_ENTRY( rtsp_session_id, RTSP_SESSION_ID, STR, 0) +OPT_ENTRY( rtsp_client_cseq, RTSP_CLIENT_CSEQ, LNG, 0) +OPT_ENTRY( rtsp_server_cseq, RTSP_SERVER_CSEQ, LNG, 0) +OPT_ENTRY( rtsp_cseq_recv, RTSP_CSEQ_RECV, LNG, 0) +#endif + +#if LCURL_CURL_VER_GE(7,50,1) +OPT_ENTRY( http_version, HTTP_VERSION, LNG, 0) +#endif + +#if LCURL_CURL_VER_GE(7,52,0) +OPT_ENTRY( proxy_ssl_verifyresult, PROXY_SSL_VERIFYRESULT, LNG, 0) +OPT_ENTRY( protocol, PROTOCOL, LNG, 0) +OPT_ENTRY( scheme, SCHEME, STR, 0) +#endif + +#if LCURL_CURL_VER_GE(7,55,0) +OPT_ENTRY( content_length_download_t, CONTENT_LENGTH_DOWNLOAD_T, OFF, 0) +OPT_ENTRY( content_length_upload_t, CONTENT_LENGTH_UPLOAD_T, OFF, 0) +OPT_ENTRY( size_download_t, SIZE_DOWNLOAD_T, OFF, 0) +OPT_ENTRY( size_upload_t, SIZE_UPLOAD_T, OFF, 0) +OPT_ENTRY( speed_download_t, SPEED_DOWNLOAD_T, OFF, 0) +OPT_ENTRY( speed_upload_t, SPEED_UPLOAD_T, OFF, 0) +#endif + +#if LCURL_CURL_VER_GE(7,59,0) +OPT_ENTRY( filetime_t, FILETIME_T, OFF, 0) +#endif + +#if LCURL_CURL_VER_GE(7,61,0) +OPT_ENTRY(appconnect_time_t, APPCONNECT_TIME_T, OFF, 0) +OPT_ENTRY(connect_time_t, CONNECT_TIME_T, OFF, 0) +OPT_ENTRY(namelookup_time_t, NAMELOOKUP_TIME_T, OFF, 0) +OPT_ENTRY(pretransfer_time_t, PRETRANSFER_TIME_T, OFF, 0) +OPT_ENTRY(redirect_time_t, REDIRECT_TIME_T, OFF, 0) +OPT_ENTRY(starttransfer_time_t, STARTTRANSFER_TIME_T, OFF, 0) +OPT_ENTRY(total_time_t, TOTAL_TIME_T, OFF, 0) +#endif + +#if LCURL_CURL_VER_GE(7,66,0) +OPT_ENTRY(retry_after, RETRY_AFTER, OFF, 0) +#endif + +#if LCURL_CURL_VER_GE(7,72,0) +OPT_ENTRY(effective_method, EFFECTIVE_METHOD, STR, 0) +#endif + +#if LCURL_CURL_VER_GE(7,73,0) +OPT_ENTRY(proxy_error, PROXY_ERROR, LNG, 0) +#endif + +// OPT_ENTRY( PRIVATE, void ) +// OPT_ENTRY( TLS_SSL_PTR, struct curl_tlssessioninfo ** +// OPT_ENTRY( TLS_SESSION, struct curl_tlssessioninfo * diff --git a/watchdog/third_party/lua-curl/src/lcmime.c b/watchdog/third_party/lua-curl/src/lcmime.c new file mode 100644 index 0000000..90fd6b4 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcmime.c @@ -0,0 +1,686 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2017-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lcmime.h" +#include "lceasy.h" +#include "lcerror.h" +#include "lcutils.h" + +/* API Notes. + * 1. Each mime can be root or child. If mime is a child (subpart) then curl free it + * when parent mime is freed or when remove this part from parent. There no way reuse same mime. + * Its not clear is it possible use mime created by one easy handle when do preform in another. + * `m=e1:mime() e2:setopt_httpmime(m) e1:close() e2:perform()` + * + * // Attach child to root (root also can have parent) + * curl_mime_subparts(root, child); + * + * // curl free `child` and all its childs + * curl_mime_subparts(root, other_child_or_null); + * + * // forbidden + * curl_mime_free(child); + */ + +#if LCURL_CURL_VER_GE(7,56,0) + +#define LCURL_MIME_NAME LCURL_PREFIX" MIME" +static const char *LCURL_MIME = LCURL_MIME_NAME; + +#define LCURL_MIME_PART_NAME LCURL_PREFIX" MIME Part" +static const char *LCURL_MIME_PART = LCURL_MIME_PART_NAME; + +//{ Free mime and subparts + +static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it); + +static lcurl_mime_t* lcurl_mime_part_get_subparts(lua_State *L, lcurl_mime_part_t *part){ + lcurl_mime_t *sub = NULL; + + if(LUA_NOREF != part->subpart_ref){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, part->subpart_ref); + sub = lcurl_getmime_at(L, -1); + lua_pop(L, 1); + } + + return sub; +} + +static int lcurl_mime_part_reset(lua_State *L, lcurl_mime_part_t *p){ + p->part = NULL; + + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); + + p->headers_ref = p->rbuffer.ref = p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + + /*free only if we have no parents*/ + lcurl_mime_part_remove_subparts(L, p, 0); + + return 0; +} + +static int lcurl_mime_reset(lua_State *L, lcurl_mime_t *p){ + lcurl_mime_part_t *ptr; + + /* reset all parts*/ + for(ptr = p->parts; ptr; ptr=ptr->next){ + lcurl_mime_part_reset(L, ptr); + } + + if(LUA_NOREF != p->storage){ + p->storage = lcurl_storage_free(L, p->storage); + } + + p->parts = p->parent = NULL; + p->mime = NULL; + + /* remove weak reference to easy */ + lua_pushnil(L); + lua_rawsetp(L, LCURL_MIME_EASY, p); + + return 0; +} + +static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it){ + lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, p); + if(sub){ + assert(LUA_NOREF != p->subpart_ref); + /* detach `subpart` mime from current mime part */ + /* if set `sub->parent = NULL` then gc for mime will try free curl_mime_free. */ + + luaL_unref(L, LCURL_LUA_REGISTRY, p->subpart_ref); + p->subpart_ref = LUA_NOREF; + + if(p->part && free_it){ + curl_mime_subparts(p->part, NULL); + } + + /* seems curl_mime_subparts(h, NULL) free asubparts. + so we have to invalidate all reference to all nested objects (part/mime). + NOTE. All resources already feed. So just need set all pointers to NULL + and free all Lua resources (like references and storages) + */ + { + lcurl_mime_part_t *ptr; + /* reset all parts*/ + for(ptr = sub->parts; ptr; ptr=ptr->next){ + lcurl_mime_part_remove_subparts(L, p, 0); + } + lcurl_mime_reset(L, sub); + } + } +} + +//} + +int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v){ + lcurl_mime_part_t *part; + for(part = p->parts; part; part=part->next){ + lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, part); + if(sub) lcurl_mime_set_lua(L, sub, v); + part->L = v; + } + return 0; +} + +#define IS_NILORSTR(L, i) (lua_type(L, i) == LUA_TSTRING) || (lua_type(L, i) == LUA_TNIL) +#define IS_TABLE(L, i) lua_type(L, i) == LUA_TTABLE +#define IS_FALSE(L, i) ((lua_type(L, i) == LUA_TBOOLEAN) && (!lua_toboolean(L, i))) || lutil_is_null(L,i) +#define IS_OPTSTR(L, i) (IS_FALSE(L, i)) || (IS_NILORSTR(L, i)) + +static int lutil_isarray(lua_State *L, int i){ + int ret = 0; + i = lua_absindex(L, i); + lua_pushnil(L); + if(lua_next(L, i)){ + ret = lua_isnumber(L, -2); + lua_pop(L, 2); + } + return ret; +} + +static int lcurl_mime_part_assign(lua_State *L, int part, const char *method){ + int top = lua_gettop(L); + + lua_pushvalue(L, part); + lua_insert(L, -2); + lua_getfield(L, -2, method); + lua_insert(L, -3); + lua_call(L, 2, LUA_MULTRET); + + return lua_gettop(L) - top + 1; +} + +static const char *lcurl_mime_part_fields[] = { + "data", "filedata", "name", "filename", "headers", "encoder", "type", NULL +}; + +static int lcurl_mime_part_assing_table(lua_State *L, int part, int t){ + int top = lua_gettop(L); + const char *method; int i; + + part = lua_absindex(L, part); + t = lua_absindex(L, t); + + if(lutil_isarray(L, t)){ + int ret; + lua_pushvalue(L, t); + ret = lcurl_mime_part_assign(L, part, "headers"); + if(ret != 1) return ret; + + lua_pop(L, 1); + + assert(top == lua_gettop(L)); + } + else{ + for(i=0;method = lcurl_mime_part_fields[i]; ++i){ + lua_getfield(L, t, method); + if(!lua_isnil(L, -1)){ + int ret = lcurl_mime_part_assign(L, part, method); + if(ret != 1) return ret; + } + lua_pop(L, 1); + + assert(top == lua_gettop(L)); + } + + lua_getfield(L, t, "subparts"); + if(!lua_isnil(L, -1)){ + if(IS_FALSE(L, -1) || lcurl_getmime_at(L, -1)){ + int ret = lcurl_mime_part_assign(L, part, "subparts"); + if(ret != 1) return ret; + } + } + lua_pop(L, 1); + assert(top == lua_gettop(L)); + } + + return 0; +} + +//{ MIME + +static lcurl_mime_part_t* lcurl_mime_parts_append(lcurl_mime_t *m, lcurl_mime_part_t *p){ + if(!m->parts) m->parts = p; + else{ + lcurl_mime_part_t *ptr = m->parts; + while(ptr->next)ptr = ptr->next; + ptr->next = p; + } + return p; +} + +static lcurl_mime_part_t* lcurl_mime_parts_find(lcurl_mime_t *m, lcurl_mime_part_t *p){ + lcurl_mime_part_t *ptr; + + for(ptr = m->parts; ptr; ptr = ptr->next){ + if(ptr == p) return p; + } + + return NULL; +} + +int lcurl_mime_create(lua_State *L, int error_mode){ + //! @todo make this function as method of easy handle + lcurl_easy_t *e = lcurl_geteasy(L); + + lcurl_mime_t *p = lutil_newudatap(L, lcurl_mime_t, LCURL_MIME); + + p->mime = curl_mime_init(e->curl); + + //! @todo return more accurate error category/code + if(!p->mime) return lcurl_fail_ex(L, error_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); + + p->storage = lcurl_storage_init(L); + p->err_mode = error_mode; + p->parts = p->parent = NULL; + + /* weak reference from mime to easy handle */ + lua_pushvalue(L, 1); + lua_rawsetp(L, LCURL_MIME_EASY, (void*)p); + + return 1; +} + +lcurl_mime_t *lcurl_getmime_at(lua_State *L, int i){ + lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, i, LCURL_MIME); + luaL_argcheck (L, p != NULL, i, LCURL_MIME_NAME" object expected"); + luaL_argcheck (L, p->mime != NULL, i, LCURL_MIME_NAME" object freed"); + return p; +} + +static int lcurl_mime_to_s(lua_State *L){ + lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, 1, LCURL_MIME); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_NAME" object expected"); + + lua_pushfstring(L, LCURL_MIME_NAME" (%p)%s", (void*)p, + p->mime ? (p->parent ? " (subpart)" : "") : " (freed)" + ); + return 1; +} + +static int lcurl_mime_free(lua_State *L){ + lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, 1, LCURL_MIME); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_NAME" object expected"); + + if((p->mime) && (NULL == p->parent)){ + curl_mime_free(p->mime); + } + + return lcurl_mime_reset(L, p); +} + +static int lcurl_mime_addpart(lua_State *L){ + lcurl_mime_t *p = lcurl_getmime(L); + int ret; + + lua_settop(L, 2); + + ret = lcurl_mime_part_create(L, p->err_mode); + if(ret != 1) return ret; + + /* store mime part in storage */ + lcurl_storage_preserve_value(L, p->storage, lua_absindex(L, -1)); + lcurl_mime_parts_append(p, lcurl_getmimepart_at(L, -1)); + + if(lua_istable(L, 2)){ + ret = lcurl_mime_part_assing_table(L, 3, 2); + if(ret) return ret; + } + + return 1; +} + +static int lcurl_mime_easy(lua_State *L){ + lcurl_mime_t *p = lcurl_getmime(L); + lua_rawgetp(L, LCURL_MIME_EASY, p); + return 1; +} + +//} + +//{ MIME Part + +int lcurl_mime_part_create(lua_State *L, int error_mode){ + //! @todo make this function as method of mime handle + lcurl_mime_t *m = lcurl_getmime(L); + + lcurl_mime_part_t *p = lutil_newudatap(L, lcurl_mime_part_t, LCURL_MIME_PART); + + p->part = curl_mime_addpart(m->mime); + + //! @todo return more accurate error category/code + if(!p->part) return lcurl_fail_ex(L, error_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); + + p->rbuffer.ref = p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + p->err_mode = error_mode; + p->subpart_ref = p->headers_ref = LUA_NOREF; + p->parent = m; + + return 1; +} + +lcurl_mime_part_t *lcurl_getmimepart_at(lua_State *L, int i){ + lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, i, LCURL_MIME_PART); + luaL_argcheck (L, p != NULL, i, LCURL_MIME_PART_NAME" object expected"); + luaL_argcheck (L, p->part != NULL, i, LCURL_MIME_PART_NAME" object freed"); + return p; +} + +static int lcurl_mime_part_to_s(lua_State *L){ + lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, 1, LCURL_MIME_PART); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_PART_NAME" object expected"); + + lua_pushfstring(L, LCURL_MIME_PART_NAME" (%p)%s", (void*)p, p->part ? "" : " (freed)"); + return 1; +} + +static int lcurl_mime_part_free(lua_State *L){ + lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, 1, LCURL_MIME_PART); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_PART_NAME" object expected"); + + lcurl_mime_part_reset(L, p); + + return 0; +} + +static int lcurl_mime_part_assing_ext(lua_State *L, int part, int i){ +#define UNSET_VALUE (const char*)-1 + + const char *mime_type = NULL, *mime_name = NULL, *mime_fname = NULL; + int headers = 0; + CURLcode ret; + lcurl_mime_part_t *p = lcurl_getmimepart_at(L, part); + + if(IS_TABLE(L, i)) headers = i; + else if (IS_OPTSTR(L, i)) { + mime_type = IS_FALSE(L, i) ? UNSET_VALUE : lua_tostring(L, i); + if(IS_TABLE(L, i+1)) headers = i+1; + else if(IS_OPTSTR(L, i+1)){ + mime_name = IS_FALSE(L, i+1) ? UNSET_VALUE : lua_tostring(L, i+1); + if(IS_TABLE(L, i+2)) headers = i+2; + else if(IS_OPTSTR(L, i+2)){ + mime_fname = IS_FALSE(L, i+2) ? UNSET_VALUE : lua_tostring(L, i+2); + if(IS_TABLE(L, i+3)) headers = i+3; + else if(IS_FALSE(L, i+3)){ + headers = -1; + } + } + } + } + + if(mime_type){ + ret = curl_mime_type(p->part, mime_type == UNSET_VALUE ? NULL : mime_type); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + if(mime_name){ + ret = curl_mime_name(p->part, mime_name == UNSET_VALUE ? NULL : mime_name); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + if(mime_fname){ + ret = curl_mime_filename(p->part, mime_fname == UNSET_VALUE ? NULL : mime_fname); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + if(headers){ + if(-1 == headers){ + ret = curl_mime_headers(p->part, NULL, 0); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + else + return lcurl_mime_part_assing_table(L, part, headers); + } + + return 0; + +#undef UNSET_VALUE +} + +// part:data(str[, type[, name[, filename]]][, headers]) +static int lcurl_mime_part_data(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + size_t len; const char *data; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + data = NULL; + len = 0; + } + else{ + data = luaL_checklstring(L, 2, &len); + /*string too long*/ + if(len == CURL_ZERO_TERMINATED){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); + } + } + + /* curl_mime_data copies data */ + ret = curl_mime_data(p->part, data, len); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + if (lua_gettop(L) > 2){ + int res = lcurl_mime_part_assing_ext(L, 1, 3); + if (res) return res; + } + + lua_settop(L, 1); + return 1; +} + +// part:subparts(mime[, type[, name]][, headers]) +static int lcurl_mime_part_subparts(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + lcurl_mime_t *mime = lcurl_getmime_at(L, 2); + CURLcode ret; + + /* we can attach mime to only one part */ + if(mime->parent){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); + } + + /* if we already have one subpart then libcurl free it so we can not use any references to it */ + lcurl_mime_part_remove_subparts(L, p, 1); + + ret = curl_mime_subparts(p->part, mime->mime); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_pushvalue(L, 2); + p->subpart_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + mime->parent = p; + + if (lua_gettop(L) > 2){ + int res = lcurl_mime_part_assing_ext(L, 1, 3); + if (res) return res; + } + + lua_settop(L, 1); + return 1; +} + +// part:filedata(path[, type[, name[, filename]]][, headers]) +static int lcurl_mime_part_filedata(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *data = luaL_checkstring(L, 2); + CURLcode ret; + + ret = curl_mime_filedata(p->part, data); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + if (lua_gettop(L) > 2){ + int res = lcurl_mime_part_assing_ext(L, 1, 3); + if (res) return res; + } + + lua_settop(L, 1); + return 1; +} + +// part:headers(t) +static int lcurl_mime_part_headers(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + struct curl_slist *list; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + list = NULL; + } + else{ + list = lcurl_util_to_slist(L, 2); + luaL_argcheck(L, list || IS_TABLE(L, 2), 2, "array or null expected"); + } + + ret = curl_mime_headers(p->part, list, 1); + + if(ret != CURLE_OK){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:type(t) +static int lcurl_mime_part_type(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_type; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_type = NULL; + } + else{ + mime_type = luaL_checkstring(L, 2); + } + + ret = curl_mime_type(p->part, mime_type); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:name(t) +static int lcurl_mime_part_name(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_name; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_name = NULL; + } + else{ + mime_name = luaL_checkstring(L, 2); + } + ret = curl_mime_name(p->part, mime_name); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:filename(t) +static int lcurl_mime_part_filename(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_name; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_name = NULL; + } + else{ + mime_name = luaL_checkstring(L, 2); + } + ret = curl_mime_filename(p->part, mime_name); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:encoder(t) +static int lcurl_mime_part_encoder(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_encode; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_encode = NULL; + } + else{ + mime_encode = luaL_checkstring(L, 2); + } + ret = curl_mime_encoder(p->part, mime_encode); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +//} + +static const struct luaL_Reg lcurl_mime_methods[] = { + + {"addpart", lcurl_mime_addpart }, + {"easy", lcurl_mime_easy }, + + {"free", lcurl_mime_free }, + {"__gc", lcurl_mime_free }, + {"__tostring", lcurl_mime_to_s }, + + {NULL,NULL} +}; + +static const struct luaL_Reg lcurl_mime_part_methods[] = { + + {"subparts", lcurl_mime_part_subparts }, + {"data", lcurl_mime_part_data }, + {"filedata", lcurl_mime_part_filedata }, + {"headers", lcurl_mime_part_headers }, + {"name", lcurl_mime_part_name }, + {"filename", lcurl_mime_part_filename }, + {"type", lcurl_mime_part_type }, + {"encoder", lcurl_mime_part_encoder }, + + + {"free", lcurl_mime_part_free }, + {"__gc", lcurl_mime_part_free }, + {"__tostring", lcurl_mime_part_to_s }, + + {NULL,NULL} +}; + +static int lcurl_pushvalues(lua_State *L, int nup) { + assert(lua_gettop(L) >= nup); + + if (nup > 0) { + int b = lua_absindex(L, -nup); + int e = lua_absindex(L, -1); + int i; + + lua_checkstack(L, nup); + + for(i = b; i <= e; ++i) + lua_pushvalue(L, i); + } + + return nup; +} + +#endif + +void lcurl_mime_initlib(lua_State *L, int nup){ +#if LCURL_CURL_VER_GE(7,56,0) + lcurl_pushvalues(L, nup); + + if(!lutil_createmetap(L, LCURL_MIME, lcurl_mime_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + if(!lutil_createmetap(L, LCURL_MIME_PART, lcurl_mime_part_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + +#else + lua_pop(L, nup); +#endif +} + diff --git a/watchdog/third_party/lua-curl/src/lcmime.h b/watchdog/third_party/lua-curl/src/lcmime.h new file mode 100644 index 0000000..6dbe9c5 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcmime.h @@ -0,0 +1,66 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2017-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCMIME_H_ +#define _LCMIME_H_ + +#include "lcurl.h" +#include "lcutils.h" +#include + +void lcurl_mime_initlib(lua_State *L, int nup); + +#if LCURL_CURL_VER_GE(7,56,0) + +typedef struct lcurl_mime_part_tag{ + lua_State *L; + + lcurl_callback_t rd; + lcurl_read_buffer_t rbuffer; + + curl_mimepart *part; + + struct lcurl_mime_tag *parent; /*always set and can not be changed*/ + + int subpart_ref; + int headers_ref; + + int err_mode; + + struct lcurl_mime_part_tag *next; +}lcurl_mime_part_t; + +typedef struct lcurl_mime_tag{ + curl_mime *mime; + + int storage; + int err_mode; + + lcurl_mime_part_t *parts; + lcurl_mime_part_t *parent; /*after set there no way change it*/ +}lcurl_mime_t; + +int lcurl_mime_create(lua_State *L, int error_mode); + +lcurl_mime_t *lcurl_getmime_at(lua_State *L, int i); + +#define lcurl_getmime(L) lcurl_getmime_at((L), 1) + +int lcurl_mime_part_create(lua_State *L, int error_mode); + +lcurl_mime_part_t *lcurl_getmimepart_at(lua_State *L, int i); + +#define lcurl_getmimepart(L) lcurl_getmimepart_at((L), 1) + +int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v); + +#endif + +#endif \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/src/lcmulti.c b/watchdog/third_party/lua-curl/src/lcmulti.c new file mode 100644 index 0000000..b040083 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcmulti.c @@ -0,0 +1,670 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#if defined(_WINDOWS) || defined(_WIN32) +# define LCURL_WINDOWS +#endif + +#ifdef LCURL_WINDOWS +# include +#else +# include +#endif + +#include "lcurl.h" +#include "lceasy.h" +#include "lcmulti.h" +#include "lcerror.h" +#include "lcutils.h" +#include "lchttppost.h" + +#define LCURL_MULTI_NAME LCURL_PREFIX" Multi" +static const char *LCURL_MULTI = LCURL_MULTI_NAME; + +#if defined(DEBUG) || defined(_DEBUG) +static void lcurl__multi_validate_sate(lua_State *L, lcurl_multi_t *p){ + int top = lua_gettop(L); + + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + assert(lua_istable(L, -1)); + + lua_pushnil(L); + while(lua_next(L, -2)){ + lcurl_easy_t *e = lcurl_geteasy_at(L, -1); + void *ptr = lua_touserdata(L, -2); + + assert(e->curl == ptr); + assert(e->multi == p); + assert(e->L == p->L); + + lua_pop(L, 1); + } + + lua_pop(L, 1); + assert(lua_gettop(L) == top); +} +#else +# define lcurl__multi_validate_sate(L, p) (void*)(0) +#endif + +void lcurl__multi_assign_lua(lua_State *L, lcurl_multi_t *p, lua_State *value, int assign_easy){ + lcurl__multi_validate_sate(L, p); + + if((assign_easy)&&(p->L != value)){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + while(lua_next(L, -2)){ + lcurl_easy_t *e = lcurl_geteasy_at(L, -1); + lcurl__easy_assign_lua(L, e, value, 0); + lua_pop(L, 1); + } + lua_pop(L, 1); + } + + p->L = value; +} + +//{ + +int lcurl_multi_create(lua_State *L, int error_mode){ + lcurl_multi_t *p; + + lua_settop(L, 1); + + p = lutil_newudatap(L, lcurl_multi_t, LCURL_MULTI); + p->curl = curl_multi_init(); + p->err_mode = error_mode; + if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_INTERNAL_ERROR); + p->L = NULL; + lcurl_util_new_weak_table(L, "v"); + p->h_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + p->tm.cb_ref = p->tm.ud_ref = LUA_NOREF; + p->sc.cb_ref = p->sc.ud_ref = LUA_NOREF; + + if(lua_type(L, 1) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 1, 2, 1, p->err_mode, LCURL_ERROR_MULTI, CURLM_UNKNOWN_OPTION); + if(ret) return ret; + assert(lua_gettop(L) == 2); + } + + return 1; +} + +lcurl_multi_t *lcurl_getmulti_at(lua_State *L, int i){ + lcurl_multi_t *p = (lcurl_multi_t *)lutil_checkudatap (L, i, LCURL_MULTI); + luaL_argcheck (L, p != NULL, 1, LCURL_MULTI_NAME" object expected"); + return p; +} + +static int lcurl_multi_to_s(lua_State *L){ + lcurl_multi_t *p = (lcurl_multi_t *)lutil_checkudatap (L, 1, LCURL_MULTI); + lua_pushfstring(L, LCURL_MULTI_NAME" (%p)", (void*)p); + return 1; +} + +static int lcurl_multi_cleanup(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + if(p->curl){ + curl_multi_cleanup(p->curl); + p->curl = NULL; + } + + if(p->h_ref != LUA_NOREF){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + while(lua_next(L, -2)){ + lcurl_easy_t *e = lcurl_geteasy_at(L, -1); + e->multi = NULL; + lua_pop(L, 1); + } + lua_pop(L, 1); + luaL_unref(L, LCURL_LUA_REGISTRY, p->h_ref); + p->h_ref = LUA_NOREF; + } + + luaL_unref(L, LCURL_LUA_REGISTRY, p->tm.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->tm.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->sc.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->sc.ud_ref); + p->tm.cb_ref = p->tm.ud_ref = LUA_NOREF; + p->sc.cb_ref = p->sc.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + lua_pushnil(L); + lua_rawset(L, LCURL_USERVALUES); + + return 0; +} + +static int lcurl_multi_add_handle(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + lcurl_easy_t *e = lcurl_geteasy_at(L, 2); + CURLMcode code; + lua_State *curL; + + if(e->multi){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, +#if LCURL_CURL_VER_GE(7,32,1) + CURLM_ADDED_ALREADY +#else + CURLM_BAD_EASY_HANDLE +#endif + ); + } + + // From doc: + // If you have CURLMOPT_TIMERFUNCTION set in the multi handle, + // that callback will be called from within this function to ask + // for an updated timer so that your main event loop will get + // the activity on this handle to get started. + // + // So we should add easy before this call + // call chain may be like => timerfunction->socket_action->socketfunction + lua_settop(L, 2); + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushvalue(L, 2); + lua_rawsetp(L, -2, e->curl); + lua_settop(L, 1); + + // all `esay` handles have to have same L + lcurl__easy_assign_lua(L, e, p->L, 0); + + e->multi = p; + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_add_handle(p->curl, e->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code != CURLM_OK){ + // remove + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + lua_rawsetp(L, -2, e->curl); + e->multi = NULL; + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + return 1; +} + +static int lcurl_multi_remove_handle(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + lcurl_easy_t *e = lcurl_geteasy_at(L, 2); + CURLMcode code = lcurl__multi_remove_handle(L, p, e); + + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + + lua_settop(L, 1); + return 1; +} + +CURLMcode lcurl__multi_remove_handle(lua_State *L, lcurl_multi_t *p, lcurl_easy_t *e){ + CURLMcode code; + lua_State *curL; + + if(e->multi != p){ + // cURL returns CURLM_OK for such call so we do the same. + // tested on 7.37.1 + return CURLM_OK; + } + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_remove_handle(p->curl, e->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code == CURLM_OK){ + e->multi = NULL; + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + lua_rawsetp(L, -2, e->curl); + lua_pop(L, 1); + } + + return code; +} + +static int lcurl_multi_perform(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + int running_handles = 0; + CURLMcode code; + lua_State *curL; + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + while((code = curl_multi_perform(p->curl, &running_handles)) == CURLM_CALL_MULTI_PERFORM); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_pushnumber(L, running_handles); + return 1; +} + +static int lcurl_multi_info_read(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + int msgs_in_queue = 0; + CURLMsg *msg = curl_multi_info_read(p->curl, &msgs_in_queue); + int remove = lua_toboolean(L, 2); + + lcurl_easy_t *e; + if(!msg){ + lua_pushnumber(L, msgs_in_queue); + return 1; + } + + if(msg->msg == CURLMSG_DONE){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_rawgetp(L, -1, msg->easy_handle); + e = lcurl_geteasy_at(L, -1); + if(remove){ + //! @fixme We ignore any errors + CURLMcode code; + lua_State *curL; + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_remove_handle(p->curl, e->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(CURLM_OK == code){ + e->multi = NULL; + lua_pushnil(L); + lua_rawsetp(L, -3, e->curl); + } + } + if(msg->data.result == CURLE_OK){ + lua_pushboolean(L, 1); + return 2; + } + return 1 + lcurl_fail_ex(L, LCURL_ERROR_RETURN, LCURL_ERROR_EASY, msg->data.result); + } + + // @todo handle unknown message + lua_pushboolean(L, 0); + return 1; +} + +static int lcurl_multi_wait(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + CURLMcode code; + int maxfd; long ms; + + if(lua_isnoneornil(L, 2)){ + code = curl_multi_timeout(p->curl, &ms); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + } + else{ + ms = luaL_checklong(L, 2); + } + + if(ms < 0){ + /* if libcurl returns a -1 timeout here, it just means that libcurl + currently has no stored timeout value. You must not wait too long + (more than a few seconds perhaps) before you call + curl_multi_perform() again. + */ + ms = 1000; + } + +#if LCURL_CURL_VER_GE(7,28,0) + //! @todo supports extra_fds + code = curl_multi_wait(p->curl, 0, 0, ms, &maxfd); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_pushnumber(L, maxfd); + return 1; +#else + { + fd_set fdread, fdwrite, fdexcep; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + FD_ZERO(&fdexcep); + + code = curl_multi_fdset(p->curl, &fdread, &fdwrite, &fdexcep, &maxfd); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + + //if(maxfd > 0) + { + struct timeval tv; + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms % 1000) * 1000; + + maxfd = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv); + if(maxfd < 0){ + //! @fixme return error + } + } + + lua_pushnumber(L, maxfd); + return 1; + } +#endif +} + +static int lcurl_multi_timeout(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + long n; + CURLMcode code = curl_multi_timeout(p->curl, &n); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_pushnumber(L, n); + return 1; +} + +static int lcurl_multi_socket_action(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + curl_socket_t s = lcurl_opt_os_socket(L, 2, CURL_SOCKET_TIMEOUT); + CURLMcode code; int n, mask; + lua_State *curL; + + if(s == CURL_SOCKET_TIMEOUT) mask = lutil_optint64(L, 3, 0); + else mask = lutil_checkint64(L, 3); + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_socket_action(p->curl, s, mask, &n); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_pushinteger(L, n); + return 1; +} + +//{ OPTIONS +static int lcurl_opt_set_long_(lua_State *L, int opt){ + lcurl_multi_t *p = lcurl_getmulti(L); + long val; CURLMcode code; + + if(lua_isboolean(L, 2)) val = lua_toboolean(L, 2); + else{ + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number or boolean expected"); + val = luaL_checklong(L, 2); + } + + code = curl_multi_setopt(p->curl, opt, val); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_settop(L, 1); + return 1; +} + +static int lcurl_opt_set_string_array_(lua_State *L, int opt){ + lcurl_multi_t *p = lcurl_getmulti(L); + CURLMcode code; + int n; + + if (lutil_is_null(L, 2)) { + n = 0; + } + else { + luaL_argcheck(L, lua_type(L, 2) == LUA_TTABLE, 2, "array expected"); + n = lua_rawlen(L, 2); + } + + if(n == 0){ + code = curl_multi_setopt(p->curl, opt, 0); + } + else{ + int i; + char const**val = malloc(sizeof(char*) * (n + 1)); + if(!val){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_OUT_OF_MEMORY); + } + for(i = 1; i <= n; ++i){ + lua_rawgeti(L, 2, i); + val[i-1] = lua_tostring(L, -1); + lua_pop(L, 1); + } + val[n] = NULL; + code = curl_multi_setopt(p->curl, opt, val); + free((void*)val); + } + + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + + lua_settop(L, 1); + return 1; +} + +#define LCURL_LNG_OPT(N, S) static int lcurl_multi_set_##N(lua_State *L){\ + return lcurl_opt_set_long_(L, CURLMOPT_##N);\ +} + +#define LCURL_STR_ARR_OPT(N, S) static int lcurl_multi_set_##N(lua_State *L){\ + return lcurl_opt_set_string_array_(L, CURLMOPT_##N);\ +} + +#define OPT_ENTRY(L, N, T, S) LCURL_##T##_OPT(N, S) + +#include "lcoptmulti.h" + +#undef OPT_ENTRY +#undef LCURL_LNG_OPT +#undef LCURL_STR_ARR_OPT + +//} + +//{ CallBack + +static int lcurl_multi_set_callback(lua_State *L, + lcurl_multi_t *p, lcurl_callback_t *c, + int OPT_CB, int OPT_UD, + const char *method, void *func +) +{ + lcurl_set_callback(L, c, 2, method); + + curl_multi_setopt(p->curl, OPT_CB, (c->cb_ref == LUA_NOREF)?0:func); + curl_multi_setopt(p->curl, OPT_UD, (c->cb_ref == LUA_NOREF)?0:p); + + return 1; +} + +//{ Timer + +int lcurl_multi_timer_callback(CURLM *multi, long ms, void *arg){ + lcurl_multi_t *p = arg; + lua_State *L = p->L; + int n, top, ret = 0; + + assert(NULL != p->L); + + top = lua_gettop(L); + n = lcurl_util_push_cb(L, &p->tm); + + lua_pushnumber(L, ms); + if(lua_pcall(L, n, LUA_MULTRET, 0)){ + assert(lua_gettop(L) >= top); + lua_settop(L, top); //! @todo + // lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + // lua_insert(L, top+1); + return -1; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1)){ + lua_settop(L, top); + return -1; + } + + if(lua_isboolean(L, top + 1)) + ret = lua_toboolean(L, top + 1)?0:-1; + else ret = lua_tointeger(L, top + 1); + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_multi_set_TIMERFUNCTION(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + return lcurl_multi_set_callback(L, p, &p->tm, + CURLMOPT_TIMERFUNCTION, CURLMOPT_TIMERDATA, + "timer", lcurl_multi_timer_callback + ); +} + +//} + +//{ Socket + +static int lcurl_multi_socket_callback(CURL *easy, curl_socket_t s, int what, void *arg, void *socketp){ + lcurl_multi_t *p = arg; + lua_State *L = p->L; + lcurl_easy_t *e; + int n, top; + + assert(NULL != p->L); + + top = lua_gettop(L); + n = lcurl_util_push_cb(L, &p->sc); + + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_rawgetp(L, -1, easy); + e = lcurl_geteasy_at(L, -1); + lua_remove(L, -2); + lcurl_push_os_socket(L, s); + lua_pushinteger(L, what); + + if(lua_pcall(L, n+2, 0, 0)){ + assert(lua_gettop(L) >= top); + lua_settop(L, top); + return -1; //! @todo break perform + } + + lua_settop(L, top); + return 0; +} + +static int lcurl_multi_set_SOCKETFUNCTION(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + return lcurl_multi_set_callback(L, p, &p->sc, + CURLMOPT_SOCKETFUNCTION, CURLMOPT_SOCKETDATA, + "socket", lcurl_multi_socket_callback + ); +} + +//} + +//} + +static int lcurl_multi_setopt(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + int opt; + + luaL_checkany(L, 2); + + if(lua_type(L, 2) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 2, 1, 0, p->err_mode, LCURL_ERROR_MULTI, CURLM_UNKNOWN_OPTION); + if(ret) return ret; + lua_settop(L, 1); + return 1; + } + + opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S) case CURLMOPT_##N: return lcurl_multi_set_##N(L); + switch(opt){ + #include "lcoptmulti.h" + OPT_ENTRY(timerfunction, TIMERFUNCTION, TTT, 0) + OPT_ENTRY(socketfunction, SOCKETFUNCTION, TTT, 0) + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_UNKNOWN_OPTION); +} + +static int lcurl_multi_setdata(lua_State *L){ + lua_settop(L, 2); + lua_pushvalue(L, 1); + lua_insert(L, 2); + lua_rawset(L, LCURL_USERVALUES); + return 1; +} + +static int lcurl_multi_getdata(lua_State *L){ + lua_settop(L, 1); + lua_rawget(L, LCURL_USERVALUES); + return 1; +} + +//} + +static const struct luaL_Reg lcurl_multi_methods[] = { + {"add_handle", lcurl_multi_add_handle }, + {"remove_handle", lcurl_multi_remove_handle }, + {"perform", lcurl_multi_perform }, + {"info_read", lcurl_multi_info_read }, + {"setopt", lcurl_multi_setopt }, + {"wait", lcurl_multi_wait }, + {"timeout", lcurl_multi_timeout }, + {"socket_action", lcurl_multi_socket_action }, + { "__tostring", lcurl_multi_to_s }, + +#define OPT_ENTRY(L, N, T, S) { "setopt_"#L, lcurl_multi_set_##N }, + #include "lcoptmulti.h" + OPT_ENTRY(timerfunction, TIMERFUNCTION, TTT, 0) + OPT_ENTRY(socketfunction, SOCKETFUNCTION, TTT, 0) +#undef OPT_ENTRY + + { "setdata", lcurl_multi_setdata }, + { "getdata", lcurl_multi_getdata }, + + {"close", lcurl_multi_cleanup }, + {"__gc", lcurl_multi_cleanup }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_multi_opt[] = { +#define OPT_ENTRY(L, N, T, S) { "OPT_MULTI_"#N, CURLMOPT_##N }, + #include "lcoptmulti.h" + OPT_ENTRY(timerfunction, TIMERFUNCTION, TTT, 0) + OPT_ENTRY(socketfunction, SOCKETFUNCTION, TTT, 0) +#undef OPT_ENTRY + + {NULL, 0} +}; + +void lcurl_multi_initlib(lua_State *L, int nup){ + if(!lutil_createmetap(L, LCURL_MULTI, lcurl_multi_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_multi_opt); +} diff --git a/watchdog/third_party/lua-curl/src/lcmulti.h b/watchdog/third_party/lua-curl/src/lcmulti.h new file mode 100644 index 0000000..89c8723 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcmulti.h @@ -0,0 +1,50 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCMULTI_H_ +#define _LCMULTI_H_ + +#include "lcurl.h" +#include "lcutils.h" + +typedef struct lcurl_multi_tag{ + CURLM *curl; + lua_State *L; + int err_mode; + int h_ref; + lcurl_callback_t tm; + lcurl_callback_t sc; +}lcurl_multi_t; + + +#if LCURL_CC_SUPPORT_FORWARD_TYPEDEF +typedef struct lcurl_multi_tag lcurl_multi_t; +#else +struct lcurl_easy_tag; +#define lcurl_easy_t struct lcurl_easy_tag +#endif + +int lcurl_multi_create(lua_State *L, int error_mode); + +lcurl_multi_t *lcurl_getmulti_at(lua_State *L, int i); + +#define lcurl_getmulti(L) lcurl_getmulti_at((L),1) + +void lcurl_multi_initlib(lua_State *L, int nup); + +void lcurl__multi_assign_lua(lua_State *L, lcurl_multi_t *p, lua_State *value, int assign_easy); + +CURLMcode lcurl__multi_remove_handle(lua_State *L, lcurl_multi_t *p, lcurl_easy_t *e); + +#if !LCURL_CC_SUPPORT_FORWARD_TYPEDEF +#undef lcurl_easy_t +#endif + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcopteasy.h b/watchdog/third_party/lua-curl/src/lcopteasy.h new file mode 100644 index 0000000..8155667 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcopteasy.h @@ -0,0 +1,557 @@ +/* Before version 7.17.0, strings were not copied. + Instead the user was forced keep them available + until libcurl no longer needed them. +*/ + +#ifndef LCURL_STORE_STRING +# if LCURL_CURL_VER_GE(7,17,0) +# define LCURL_STORE_STRING 0 +# else +# define LCURL_STORE_STRING 1 +# endif +#endif + +#ifndef OPT_ENTRY +# define OPT_ENTRY(a,b,c,d,e) +# define OPT_ENTRY_IS_NULL +#endif + +#ifndef FLG_ENTRY +# define FLG_ENTRY(a) +# define FLG_ENTRY_IS_NULL +#endif + +#ifndef LCURL_DEFAULT_VALUE +# define LCURL_DEFAULT_VALUE 0 +#endif + +//{ Reset system macros + +#ifdef TCP_FASTOPEN +# define LCURL__TCP_FASTOPEN TCP_FASTOPEN +# undef TCP_FASTOPEN +#endif + +#ifdef TCP_KEEPIDLE +# define LCURL__TCP_KEEPIDLE TCP_KEEPIDLE +# undef TCP_KEEPIDLE +#endif + +#ifdef TCP_KEEPINTVL +# define LCURL__TCP_KEEPINTVL TCP_KEEPINTVL +# undef TCP_KEEPINTVL +#endif + +#ifdef TCP_NODELAY +# define LCURL__TCP_NODELAY TCP_NODELAY +# undef TCP_NODELAY +#endif + +#ifdef TCP_KEEPALIVE +# define LCURL__TCP_KEEPALIVE TCP_KEEPALIVE +# undef TCP_KEEPALIVE +#endif + +#ifdef BUFFERSIZE +# define LCURL__BUFFERSIZE BUFFERSIZE +# undef BUFFERSIZE +#endif + +#ifdef INTERFACE +# define LCURL__INTERFACE INTERFACE +# undef INTERFACE +#endif + +//} + +OPT_ENTRY( verbose, VERBOSE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( header, HEADER, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( noprogress, NOPROGRESS, LNG, 0, 1 ) +OPT_ENTRY( nosignal, NOSIGNAL, LNG, 0, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,21,0) +OPT_ENTRY( wildcardmatch, WILDCARDMATCH, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif + +OPT_ENTRY( url, URL, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( failonerror, FAILONERROR, LNG, 0, LCURL_DEFAULT_VALUE ) + +OPT_ENTRY( protocols, PROTOCOLS, LNG, 0, CURLPROTO_ALL ) +OPT_ENTRY( redir_protocols, REDIR_PROTOCOLS, LNG, 0, CURLPROTO_ALL ) /*! @fixme All protocols except for FILE and SCP */ +OPT_ENTRY( proxy, PROXY, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxyport, PROXYPORT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxytype, PROXYTYPE, LNG, 0, CURLPROXY_HTTP ) +OPT_ENTRY( noproxy, NOPROXY, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( httpproxytunnel, HTTPPROXYTUNNEL, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( socks5_gssapi_service, SOCKS5_GSSAPI_SERVICE, STR, LCURL_STORE_STRING, "rcmd/server-fqdn" ) +OPT_ENTRY( socks5_gssapi_nec, SOCKS5_GSSAPI_NEC, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @check doc says nothing */ +OPT_ENTRY( interface, INTERFACE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( localport, LOCALPORT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( localportrange, LOCALPORTRANGE, LNG, 0, 1 ) +OPT_ENTRY( dns_cache_timeout, DNS_CACHE_TIMEOUT, LNG, 0, 60 ) + +#if !LCURL_CURL_VER_GE(7,65,0) +OPT_ENTRY( dns_use_global_cache, DNS_USE_GLOBAL_CACHE, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif + +#if LCURL_CURL_VER_GE(7,25,0) +OPT_ENTRY( dns_servers, DNS_SERVERS, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( buffersize, BUFFERSIZE, LNG, 0, CURL_MAX_WRITE_SIZE ) +OPT_ENTRY( port, PORT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( tcp_nodelay, TCP_NODELAY, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( address_scope, ADDRESS_SCOPE, LNG, 0, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,25,0) +OPT_ENTRY( tcp_keepalive, TCP_KEEPALIVE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( tcp_keepidle, TCP_KEEPIDLE, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @check doc says nothing */ +OPT_ENTRY( tcp_keepintvl, TCP_KEEPINTVL, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @check doc says nothing */ +#endif + +OPT_ENTRY( netrc, NETRC, LNG, 0, CURL_NETRC_IGNORED ) +OPT_ENTRY( netrc_file, NETRC_FILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( userpwd, USERPWD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxyuserpwd, PROXYUSERPWD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( username, USERNAME, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( password, PASSWORD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,31,0) +OPT_ENTRY( login_options, LOGIN_OPTIONS, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( proxyusername, PROXYUSERNAME, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxypassword, PROXYPASSWORD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( httpauth, HTTPAUTH, LNG, 0, CURLAUTH_BASIC ) +#if LCURL_CURL_VER_GE(7,21,4) +OPT_ENTRY( tlsauth_username, TLSAUTH_USERNAME, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( tlsauth_password, TLSAUTH_PASSWORD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( tlsauth_type, TLSAUTH_TYPE, STR, 0, "" ) +#endif +OPT_ENTRY( proxyauth, PROXYAUTH, LNG, 0, CURLAUTH_BASIC ) +#if LCURL_CURL_VER_GE(7,31,0) +OPT_ENTRY( sasl_ir, SASL_IR, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,33,0) +OPT_ENTRY( xoauth2_bearer, XOAUTH2_BEARER, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif + +OPT_ENTRY( autoreferer, AUTOREFERER, LNG, 0, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,21,6) +OPT_ENTRY( accept_encoding, ACCEPT_ENCODING, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( transfer_encoding, TRANSFER_ENCODING, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( followlocation, FOLLOWLOCATION, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( unrestricted_auth, UNRESTRICTED_AUTH, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( maxredirs, MAXREDIRS, LNG, 0, -1 ) +OPT_ENTRY( postredir, POSTREDIR, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( put, PUT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( post, POST, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( referer, REFERER, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( useragent, USERAGENT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,37,0) +OPT_ENTRY( headeropt, HEADEROPT, LNG, 0, CURLHEADER_UNIFIED ) +#endif +OPT_ENTRY( httpheader, HTTPHEADER, LST, 0, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,37,0) +OPT_ENTRY( proxyheader, PROXYHEADER, LST, 0, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( http200aliases, HTTP200ALIASES, LST, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookie, COOKIE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookiefile, COOKIEFILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookiejar, COOKIEJAR, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookiesession, COOKIESESSION, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookielist, COOKIELIST, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( httpget, HTTPGET, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( http_version, HTTP_VERSION, LNG, 0, CURL_HTTP_VERSION_NONE ) +OPT_ENTRY( ignore_content_length, IGNORE_CONTENT_LENGTH, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( http_content_decoding, HTTP_CONTENT_DECODING, LNG, 0, 1 ) +OPT_ENTRY( http_transfer_decoding, HTTP_TRANSFER_DECODING, LNG, 0, 1 ) +#if LCURL_CURL_VER_GE(7,36,0) +OPT_ENTRY( expect_100_timeout_ms, EXPECT_100_TIMEOUT_MS, LNG, 0, 1000 ) +#endif + +#if LCURL_CURL_VER_GE(7,20,0) +OPT_ENTRY( mail_from, MAIL_FROM, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) /*! @check doc says `blank` */ +OPT_ENTRY( mail_rcpt, MAIL_RCPT, LST, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,25,0) +OPT_ENTRY( mail_auth, MAIL_AUTH, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif + +OPT_ENTRY( tftp_blksize, TFTP_BLKSIZE, LNG, 0, 512 ) + +OPT_ENTRY( ftpport, FTPPORT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( quote, QUOTE, LST, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( postquote, POSTQUOTE, LST, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( prequote, PREQUOTE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( dirlistonly, DIRLISTONLY, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( append, APPEND, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( ftp_use_eprt, FTP_USE_EPRT, LNG, 0, LCURL_DEFAULT_VALUE )/*! @check doc says nothing */ +OPT_ENTRY( ftp_use_epsv, FTP_USE_EPSV, LNG, 0, 1 ) +#if LCURL_CURL_VER_GE(7,20,0) +OPT_ENTRY( ftp_use_pret, FTP_USE_PRET, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( ftp_create_missing_dirs, FTP_CREATE_MISSING_DIRS, LNG, 0, CURLFTP_CREATE_DIR_NONE ) +OPT_ENTRY( ftp_response_timeout, FTP_RESPONSE_TIMEOUT, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ +OPT_ENTRY( ftp_alternative_to_user, FTP_ALTERNATIVE_TO_USER, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( ftp_skip_pasv_ip, FTP_SKIP_PASV_IP, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( ftpsslauth, FTPSSLAUTH, LNG, 0, CURLFTPAUTH_DEFAULT ) +OPT_ENTRY( ftp_ssl_ccc, FTP_SSL_CCC, LNG, 0, CURLFTPSSL_CCC_NONE ) +OPT_ENTRY( ftp_account, FTP_ACCOUNT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( ftp_filemethod, FTP_FILEMETHOD, LNG, 0, CURLFTPMETHOD_MULTICWD ) + +OPT_ENTRY( transfertext, TRANSFERTEXT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxy_transfer_mode, PROXY_TRANSFER_MODE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( crlf, CRLF, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( range, RANGE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( resume_from, RESUME_FROM, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( resume_from_large, RESUME_FROM_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( customrequest, CUSTOMREQUEST, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( filetime, FILETIME, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( nobody, NOBODY, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( infilesize, INFILESIZE, LNG, 0, LCURL_DEFAULT_VALUE )/*! @fixme doc says `Unset` */ +OPT_ENTRY( infilesize_large, INFILESIZE_LARGE, LNG, 0, LCURL_DEFAULT_VALUE )/*! @fixme doc says `Unset` */ +OPT_ENTRY( upload, UPLOAD, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( maxfilesize, MAXFILESIZE, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ +OPT_ENTRY( maxfilesize_large, MAXFILESIZE_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ +OPT_ENTRY( timecondition, TIMECONDITION, LNG, 0, CURL_TIMECOND_NONE ) +OPT_ENTRY( timevalue, TIMEVALUE, LNG, 0, LCURL_DEFAULT_VALUE ) + +OPT_ENTRY( timeout, TIMEOUT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( timeout_ms, TIMEOUT_MS, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( low_speed_limit, LOW_SPEED_LIMIT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( low_speed_time, LOW_SPEED_TIME, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( max_send_speed_large, MAX_SEND_SPEED_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( max_recv_speed_large, MAX_RECV_SPEED_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( maxconnects, MAXCONNECTS, LNG, 0, 5 ) +OPT_ENTRY( fresh_connect, FRESH_CONNECT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( forbid_reuse, FORBID_REUSE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( connecttimeout, CONNECTTIMEOUT, LNG, 0, 300 ) +OPT_ENTRY( connecttimeout_ms, CONNECTTIMEOUT_MS, LNG, 0, 300000 ) +OPT_ENTRY( ipresolve, IPRESOLVE, LNG, 0, CURL_IPRESOLVE_WHATEVER ) +OPT_ENTRY( connect_only, CONNECT_ONLY, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( use_ssl, USE_SSL, LNG, 0, CURLUSESSL_NONE ) +#if LCURL_CURL_VER_GE(7,21,3) +OPT_ENTRY( resolve, RESOLVE, LST, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,33,0) +OPT_ENTRY( dns_interface, DNS_INTERFACE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( dns_local_ip4, DNS_LOCAL_IP4, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( dns_local_ip6, DNS_LOCAL_IP6, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( accepttimeout_ms, ACCEPTTIMEOUT_MS, LNG, 0, 60000 ) +#endif + +OPT_ENTRY( ssh_auth_types, SSH_AUTH_TYPES, LNG, 0, LCURL_DEFAULT_VALUE) /*! @fixme doc says `None` */ +OPT_ENTRY( ssh_host_public_key_md5, SSH_HOST_PUBLIC_KEY_MD5, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( ssh_public_keyfile, SSH_PUBLIC_KEYFILE, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( ssh_private_keyfile, SSH_PRIVATE_KEYFILE, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( ssh_knownhosts, SSH_KNOWNHOSTS, STR, 0, LCURL_DEFAULT_VALUE) + +OPT_ENTRY( new_file_perms, NEW_FILE_PERMS, LNG, 0, 0644) +OPT_ENTRY( new_directory_perms, NEW_DIRECTORY_PERMS, LNG, 0, 0755) + +OPT_ENTRY( telnetoptions, TELNETOPTIONS, LST, 0, LCURL_DEFAULT_VALUE) + +OPT_ENTRY( random_file, RANDOM_FILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( egdsocket, EGDSOCKET, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( issuercert, ISSUERCERT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( krblevel, KRBLEVEL, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) + +OPT_ENTRY( cainfo, CAINFO, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `Built-in system specific` */ +OPT_ENTRY( capath, CAPATH, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( certinfo, CERTINFO, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( crlfile, CRLFILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) + +OPT_ENTRY( sslcert, SSLCERT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( sslcerttype, SSLCERTTYPE, STR, LCURL_STORE_STRING, "PEM" ) +OPT_ENTRY( sslengine, SSLENGINE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( sslengine_default, SSLENGINE_DEFAULT, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ +OPT_ENTRY( sslkey, SSLKEY, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( sslkeytype, SSLKEYTYPE, STR, LCURL_STORE_STRING, "PEM" ) +OPT_ENTRY( sslversion, SSLVERSION, LNG, 0, CURL_SSLVERSION_DEFAULT ) +OPT_ENTRY( ssl_cipher_list, SSL_CIPHER_LIST, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,36,0) +OPT_ENTRY( ssl_enable_alpn, SSL_ENABLE_ALPN, LNG, 0, 1 ) +OPT_ENTRY( ssl_enable_npn, SSL_ENABLE_NPN, LNG, 0, 1 ) +#endif +#if LCURL_CURL_VER_GE(7,25,0) +OPT_ENTRY( ssl_options, SSL_OPTIONS, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( ssl_sessionid_cache, SSL_SESSIONID_CACHE, LNG, 0, 1 ) +OPT_ENTRY( ssl_verifyhost, SSL_VERIFYHOST, LNG, 0, 2 ) +OPT_ENTRY( ssl_verifypeer, SSL_VERIFYPEER, LNG, 0, 1 ) +OPT_ENTRY( keypasswd, KEYPASSWD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) + +#if LCURL_CURL_VER_GE(7,20,0) +OPT_ENTRY( rtsp_client_cseq, RTSP_CLIENT_CSEQ, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_request, RTSP_REQUEST, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_server_cseq, RTSP_SERVER_CSEQ, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_session_id, RTSP_SESSION_ID, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_stream_uri, RTSP_STREAM_URI, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_transport, RTSP_TRANSPORT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif + +#if LCURL_CURL_VER_GE(7,22,0) +OPT_ENTRY( gssapi_delegation, GSSAPI_DELEGATION, LNG, 0, CURLGSSAPI_DELEGATION_NONE ) +#endif + +FLG_ENTRY( SSLVERSION_DEFAULT ) +FLG_ENTRY( SSLVERSION_TLSv1 ) +FLG_ENTRY( SSLVERSION_SSLv2 ) +FLG_ENTRY( SSLVERSION_SSLv3 ) +#if LCURL_CURL_VER_GE(7,34,0) +FLG_ENTRY( SSLVERSION_TLSv1_0 ) +FLG_ENTRY( SSLVERSION_TLSv1_1 ) +FLG_ENTRY( SSLVERSION_TLSv1_2 ) +#endif +#if LCURL_CURL_VER_GE(7,52,0) +FLG_ENTRY( SSLVERSION_TLSv1_3 ) +#endif + +#if LCURL_CURL_VER_GE(7,54,0) +FLG_ENTRY( SSLVERSION_MAX_NONE ) +FLG_ENTRY( SSLVERSION_MAX_DEFAULT ) +FLG_ENTRY( SSLVERSION_MAX_TLSv1_0 ) +FLG_ENTRY( SSLVERSION_MAX_TLSv1_1 ) +FLG_ENTRY( SSLVERSION_MAX_TLSv1_2 ) +FLG_ENTRY( SSLVERSION_MAX_TLSv1_3 ) +#endif + +#if LCURL_CURL_VER_GE(7,21,4) +FLG_ENTRY( TLSAUTH_SRP ) +#endif + +FLG_ENTRY( HTTP_VERSION_NONE ) +FLG_ENTRY( HTTP_VERSION_1_0 ) +FLG_ENTRY( HTTP_VERSION_1_1 ) +#if LCURL_CURL_VER_GE(7,33,0) +FLG_ENTRY( HTTP_VERSION_2_0 ) +#endif +#if LCURL_CURL_VER_GE(7,43,0) +FLG_ENTRY( HTTP_VERSION_2 ) +#endif +#if LCURL_CURL_VER_GE(7,47,0) +FLG_ENTRY( HTTP_VERSION_2TLS ) +#endif +#if LCURL_CURL_VER_GE(7,49,0) +FLG_ENTRY( HTTP_VERSION_2_PRIOR_KNOWLEDGE ) +#endif +#if LCURL_CURL_VER_GE(7,66,0) +FLG_ENTRY( HTTP_VERSION_3 ) +#endif + +FLG_ENTRY( READFUNC_PAUSE ) /*7.18.0*/ +FLG_ENTRY( WRITEFUNC_PAUSE ) /*7.18.0*/ + +FLG_ENTRY( POLL_IN ) /*7.14.0*/ +FLG_ENTRY( POLL_INOUT ) /*7.14.0*/ +FLG_ENTRY( POLL_NONE ) /*7.14.0*/ +FLG_ENTRY( POLL_OUT ) /*7.14.0*/ +FLG_ENTRY( POLL_REMOVE ) /*7.14.0*/ +FLG_ENTRY( SOCKET_TIMEOUT ) /*7.14.0*/ + +FLG_ENTRY( CSELECT_ERR ) /*7.16.3*/ +FLG_ENTRY( CSELECT_IN ) /*7.16.3*/ +FLG_ENTRY( CSELECT_OUT ) /*7.16.3*/ + +FLG_ENTRY( IPRESOLVE_WHATEVER ) /*7.10.8*/ +FLG_ENTRY( IPRESOLVE_V4 ) /*7.10.8*/ +FLG_ENTRY( IPRESOLVE_V6 ) /*7.10.8*/ + +#if LCURL_CURL_VER_GE(7,20,0) +FLG_ENTRY( RTSPREQ_OPTIONS ) +FLG_ENTRY( RTSPREQ_DESCRIBE ) +FLG_ENTRY( RTSPREQ_ANNOUNCE ) +FLG_ENTRY( RTSPREQ_SETUP ) +FLG_ENTRY( RTSPREQ_PLAY ) +FLG_ENTRY( RTSPREQ_PAUSE ) +FLG_ENTRY( RTSPREQ_TEARDOWN ) +FLG_ENTRY( RTSPREQ_GET_PARAMETER ) +FLG_ENTRY( RTSPREQ_SET_PARAMETER ) +FLG_ENTRY( RTSPREQ_RECORD ) +FLG_ENTRY( RTSPREQ_RECEIVE ) +#endif + +#if LCURL_CURL_VER_GE(7,39,0) +OPT_ENTRY( pinnedpublickey, PINNEDPUBLICKEY, STR, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,40,0) +OPT_ENTRY( unix_socket_path, UNIX_SOCKET_PATH, STR, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,41,0) +OPT_ENTRY( ssl_verifystatus, SSL_VERIFYSTATUS, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,42,0) +OPT_ENTRY( ssl_falsestart, SSL_FALSESTART, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( path_as_is, PATH_AS_IS, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,43,0) +OPT_ENTRY( proxy_service_name, PROXY_SERVICE_NAME, STR, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( service_name, SERVICE_NAME, STR, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( pipewait, PIPEWAIT, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,45,0) +OPT_ENTRY( default_protocol, DEFAULT_PROTOCOL, STR, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,46,0) +OPT_ENTRY( stream_weight, STREAM_WEIGHT, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,48,0) +OPT_ENTRY( tftp_no_options, TFTP_NO_OPTIONS, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,49,0) +OPT_ENTRY( tcp_fastopen, TCP_FASTOPEN, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( connect_to, CONNECT_TO, LST, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,51,0) +OPT_ENTRY( keep_sending_on_error, KEEP_SENDING_ON_ERROR, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif + +#if LCURL_CURL_VER_GE(7,52,0) +OPT_ENTRY( proxy_cainfo, PROXY_CAINFO, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_capath, PROXY_CAPATH, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_ssl_verifypeer, PROXY_SSL_VERIFYPEER, LNG, 0, 1) +OPT_ENTRY( proxy_ssl_verifyhost, PROXY_SSL_VERIFYHOST, LNG, 0, 2) +OPT_ENTRY( proxy_sslversion, PROXY_SSLVERSION, LNG, 0, CURL_SSLVERSION_DEFAULT) +OPT_ENTRY( proxy_tlsauth_username, PROXY_TLSAUTH_USERNAME, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_tlsauth_password, PROXY_TLSAUTH_PASSWORD, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_tlsauth_type, PROXY_TLSAUTH_TYPE, STR, 0, "") +OPT_ENTRY( proxy_sslcert, PROXY_SSLCERT, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_sslcerttype, PROXY_SSLCERTTYPE, STR, 0, "PEM") +OPT_ENTRY( proxy_sslkey, PROXY_SSLKEY, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_sslkeytype, PROXY_SSLKEYTYPE, STR, 0, "PEM") /* default value not defined. Use same as for `SSLKEYTYPE` */ +OPT_ENTRY( proxy_keypasswd, PROXY_KEYPASSWD, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_ssl_cipher_list, PROXY_SSL_CIPHER_LIST, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_crlfile, PROXY_CRLFILE, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_ssl_options, PROXY_SSL_OPTIONS, LNG, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( pre_proxy, PRE_PROXY, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_pinnedpublickey, PROXY_PINNEDPUBLICKEY, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,53,0) +OPT_ENTRY( abstract_unix_socket, ABSTRACT_UNIX_SOCKET, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,54,0) +OPT_ENTRY( suppress_connect_headers, SUPPRESS_CONNECT_HEADERS, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,55,0) +OPT_ENTRY( request_target, REQUEST_TARGET, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( socks5_auth, SOCKS5_AUTH, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,56,0) +OPT_ENTRY( ssh_compression, SSH_COMPRESSION, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,59,0) +OPT_ENTRY( happy_eyeballs_timeout_ms,HAPPY_EYEBALLS_TIMEOUT_MS,LNG, 0, CURL_HET_DEFAULT) +OPT_ENTRY( timevalue_large, TIMEVALUE_LARGE ,OFF, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,60,0) +OPT_ENTRY(dns_shuffle_addresses, DNS_SHUFFLE_ADDRESSES, LNG, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(haproxyprotocol, HAPROXYPROTOCOL, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,61,0) +OPT_ENTRY(disallow_username_in_url, DISALLOW_USERNAME_IN_URL, LNG, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(proxy_tls13_ciphers, PROXY_TLS13_CIPHERS, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(tls13_ciphers, TLS13_CIPHERS, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,62,0) +OPT_ENTRY(upkeep_interval_ms, UPKEEP_INTERVAL_MS, LNG, 0, CURL_UPKEEP_INTERVAL_DEFAULT) +OPT_ENTRY(doh_url, DOH_URL, STR, 0, LCURL_DEFAULT_VALUE) +// thre no named value for default value. It just defined as 64kB in documentation +OPT_ENTRY(upload_buffersize, UPLOAD_BUFFERSIZE, LNG, 0, 64 * 1024) +#endif + +#if LCURL_CURL_VER_GE(7,64,0) +OPT_ENTRY(http09_allowed, HTTP09_ALLOWED, LNG, 0, 0) +#endif + +#if LCURL_CURL_VER_GE(7,64,1) +OPT_ENTRY(altsvc, ALTSVC, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(altsvc_ctrl, ALTSVC_CTRL, LNG, 0, 0) +#endif + +#if LCURL_CURL_VER_GE(7,65,0) +OPT_ENTRY(maxage_conn, MAXAGE_CONN, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,66,0) +OPT_ENTRY(sasl_authzid, SASL_AUTHZID, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,68,0) +FLG_ENTRY( PROGRESSFUNC_CONTINUE ) +#endif + +#if LCURL_CURL_VER_GE(7,69,0) +OPT_ENTRY(mail_rcpt_alllowfails, MAIL_RCPT_ALLLOWFAILS, LNG, 0, 1) +#endif + +#if LCURL_CURL_VER_GE(7,71,0) +OPT_ENTRY(sslcert_blob, SSLCERT_BLOB, BLB, 0, 0) +OPT_ENTRY(sslkey_blob, SSLKEY_BLOB, BLB, 0, 0) +OPT_ENTRY(proxy_sslcert_blob, PROXY_SSLCERT_BLOB, BLB, 0, 0) +OPT_ENTRY(proxy_sslkey_blob, PROXY_SSLKEY_BLOB, BLB, 0, 0) +OPT_ENTRY(issuercert_blob, ISSUERCERT_BLOB, BLB, 0, 0) + +OPT_ENTRY(proxy_issuercert, PROXY_ISSUERCERT, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(proxy_issuercert_blob, PROXY_ISSUERCERT_BLOB, BLB, 0, 0) +#endif + +#if LCURL_CURL_VER_GE(7,73,0) +OPT_ENTRY(ssl_ec_curves, SSL_EC_CURVES, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS +OPT_ENTRY(hsts_ctrl, HSTS_CTRL, LNG, 0, 0) +OPT_ENTRY(hsts, HSTS, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +//{ Restore system macros + +#ifdef LCURL__TCP_FASTOPEN +# define TCP_FASTOPEN LCURL__TCP_FASTOPEN +# undef LCURL__TCP_FASTOPEN +#endif + +#ifdef LCURL__TCP_KEEPIDLE +# define TCP_KEEPIDLE LCURL__TCP_KEEPIDLE +# undef LCURL__TCP_KEEPIDLE +#endif + +#ifdef LCURL__TCP_KEEPINTVL +# define TCP_KEEPINTVL LCURL__TCP_KEEPINTVL +# undef LCURL__TCP_KEEPINTVL +#endif + +#ifdef LCURL__TCP_NODELAY +# define TCP_NODELAY LCURL__TCP_NODELAY +# undef LCURL__TCP_NODELAY +#endif + +#ifdef LCURL__TCP_KEEPALIVE +# define TCP_KEEPALIVE LCURL__TCP_KEEPALIVE +# undef LCURL__TCP_KEEPALIVE +#endif + +#ifdef LCURL__BUFFERSIZE +# define BUFFERSIZE LCURL__BUFFERSIZE +# undef LCURL__BUFFERSIZE +#endif + +#ifdef LCURL__INTERFACE +# define INTERFACE LCURL__INTERFACE +# undef LCURL__INTERFACE +#endif + +//} + +#ifdef OPT_ENTRY_IS_NULL +# undef OPT_ENTRY +#endif + +#ifdef FLG_ENTRY_IS_NULL +# undef FLG_ENTRY +#endif diff --git a/watchdog/third_party/lua-curl/src/lcoptmulti.h b/watchdog/third_party/lua-curl/src/lcoptmulti.h new file mode 100644 index 0000000..8f7fd71 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcoptmulti.h @@ -0,0 +1,17 @@ + +OPT_ENTRY(pipelining, PIPELINING, LNG, 0 ) +OPT_ENTRY(maxconnects, MAXCONNECTS, LNG, 0 ) + +#if LCURL_CURL_VER_GE(7,30,0) +OPT_ENTRY(max_host_connections, MAX_HOST_CONNECTIONS, LNG, 0 ) +OPT_ENTRY(max_pipeline_length, MAX_PIPELINE_LENGTH, LNG, 0 ) +OPT_ENTRY(content_length_penalty_size, CONTENT_LENGTH_PENALTY_SIZE, LNG, 0 ) +OPT_ENTRY(chunk_length_penalty_size, CHUNK_LENGTH_PENALTY_SIZE, LNG, 0 ) +OPT_ENTRY(pipelining_site_bl, PIPELINING_SITE_BL, STR_ARR, 0 ) +OPT_ENTRY(pipelining_server_bl, PIPELINING_SERVER_BL, STR_ARR, 0 ) +OPT_ENTRY(max_total_connections, MAX_TOTAL_CONNECTIONS, LNG, 0 ) +#endif + +#if LCURL_CURL_VER_GE(7,67,0) +OPT_ENTRY(max_concurrent_streams, MAX_CONCURRENT_STREAMS, LNG, 0 ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcoptshare.h b/watchdog/third_party/lua-curl/src/lcoptshare.h new file mode 100644 index 0000000..95960bb --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcoptshare.h @@ -0,0 +1,27 @@ +#ifndef OPT_ENTRY +# define OPT_ENTRY(a,b,c,d) +# define OPT_ENTRY_IS_NULL +#endif + +#ifndef FLG_ENTRY +# define FLG_ENTRY(a) +# define FLG_ENTRY_IS_NULL +#endif + +OPT_ENTRY(share, SHARE, LNG, 0 ) +OPT_ENTRY(unshare, UNSHARE, LNG, 0 ) + +FLG_ENTRY( LOCK_DATA_COOKIE ) +FLG_ENTRY( LOCK_DATA_DNS ) +FLG_ENTRY( LOCK_DATA_SSL_SESSION ) +FLG_ENTRY( LOCK_DATA_CONNECT ) + +#ifdef OPT_ENTRY_IS_NULL +# undef OPT_ENTRY +# undef OPT_ENTRY_IS_NULL +#endif + +#ifdef FLG_ENTRY_IS_NULL +# undef FLG_ENTRY +# undef FLG_ENTRY_IS_NULL +#endif diff --git a/watchdog/third_party/lua-curl/src/lcopturl.h b/watchdog/third_party/lua-curl/src/lcopturl.h new file mode 100644 index 0000000..20fee9e --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcopturl.h @@ -0,0 +1,29 @@ +ENTRY_PART(fragment, UPART_FRAGMENT , CURLUE_NO_FRAGMENT ) +ENTRY_PART(host, UPART_HOST , CURLUE_NO_HOST ) +ENTRY_PART(options, UPART_OPTIONS , CURLUE_NO_OPTIONS ) +ENTRY_PART(password, UPART_PASSWORD , CURLUE_NO_PASSWORD ) +ENTRY_PART(path, UPART_PATH , CURLUE_OK ) +ENTRY_PART(port, UPART_PORT , CURLUE_NO_PORT ) +ENTRY_PART(query, UPART_QUERY , CURLUE_NO_QUERY ) +ENTRY_PART(scheme, UPART_SCHEME , CURLUE_NO_SCHEME ) +ENTRY_PART(url, UPART_URL , CURLUE_OK ) +ENTRY_PART(user, UPART_USER , CURLUE_NO_USER ) + +#if LCURL_CURL_VER_GE(7,65,0) +ENTRY_PART(zoneid, UPART_ZONEID , CURLUE_UNKNOWN_PART ) +#endif + +ENTRY_FLAG(DEFAULT_PORT ) +ENTRY_FLAG(NO_DEFAULT_PORT ) +ENTRY_FLAG(DEFAULT_SCHEME ) +ENTRY_FLAG(NON_SUPPORT_SCHEME ) +ENTRY_FLAG(PATH_AS_IS ) +ENTRY_FLAG(DISALLOW_USER ) +ENTRY_FLAG(URLDECODE ) +ENTRY_FLAG(URLENCODE ) +ENTRY_FLAG(APPENDQUERY ) +ENTRY_FLAG(GUESS_SCHEME ) + +#if LCURL_CURL_VER_GE(7,67,0) +ENTRY_FLAG(NO_AUTHORITY ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcshare.c b/watchdog/third_party/lua-curl/src/lcshare.c new file mode 100644 index 0000000..c7ac489 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcshare.c @@ -0,0 +1,152 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lcshare.h" +#include "lcerror.h" +#include "lcutils.h" +#include "lchttppost.h" + +#define LCURL_SHARE_NAME LCURL_PREFIX" Share" +static const char *LCURL_SHARE = LCURL_SHARE_NAME; + +//{ +int lcurl_share_create(lua_State *L, int error_mode){ + lcurl_share_t *p; + + lua_settop(L, 1); + + p = lutil_newudatap(L, lcurl_share_t, LCURL_SHARE); + p->curl = curl_share_init(); + p->err_mode = error_mode; + if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_NOMEM); + + if(lua_type(L, 1) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 1, 2, 1, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_BAD_OPTION); + if(ret) return ret; + assert(lua_gettop(L) == 2); + } + + return 1; +} + +lcurl_share_t *lcurl_getshare_at(lua_State *L, int i){ + lcurl_share_t *p = (lcurl_share_t *)lutil_checkudatap (L, i, LCURL_SHARE); + luaL_argcheck (L, p != NULL, 1, LCURL_SHARE_NAME" object expected"); + return p; +} + +static int lcurl_easy_to_s(lua_State *L){ + lcurl_share_t *p = (lcurl_share_t *)lutil_checkudatap (L, 1, LCURL_SHARE); + lua_pushfstring(L, LCURL_SHARE_NAME" (%p)", (void*)p); + return 1; +} + +static int lcurl_share_cleanup(lua_State *L){ + lcurl_share_t *p = lcurl_getshare(L); + if(p->curl){ + curl_share_cleanup(p->curl); + p->curl = NULL; + } + + return 0; +} + +//{ OPTIONS + +static int lcurl_opt_set_long_(lua_State *L, int opt){ + lcurl_share_t *p = lcurl_getshare(L); + long val; CURLSHcode code; + + if(lua_isboolean(L, 2)) val = lua_toboolean(L, 2); + else{ + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number or boolean expected"); + val = luaL_checklong(L, 2); + } + + code = curl_share_setopt(p->curl, opt, val); + if(code != CURLSHE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_SHARE, code); + } + lua_settop(L, 1); + return 1; +} + +#define LCURL_LNG_OPT(N, S) static int lcurl_share_set_##N(lua_State *L){\ + return lcurl_opt_set_long_(L, CURLSHOPT_##N);\ +} + +#define OPT_ENTRY(L, N, T, S) LCURL_##T##_OPT(N, S) + +#include "lcoptshare.h" + +#undef OPT_ENTRY +#undef LCURL_LNG_OPT + +//} + +static int lcurl_share_setopt(lua_State *L){ + lcurl_share_t *p = lcurl_getshare(L); + int opt; + + luaL_checkany(L, 2); + if(lua_type(L, 2) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 2, 1, 0, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_BAD_OPTION); + if(ret) return ret; + lua_settop(L, 1); + return 1; + } + + opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S) case CURLSHOPT_##N: return lcurl_share_set_##N(L); + switch(opt){ + #include "lcoptshare.h" + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_BAD_OPTION); +} + +//} + +static const struct luaL_Reg lcurl_share_methods[] = { + { "__tostring", lcurl_easy_to_s }, + {"setopt", lcurl_share_setopt }, + +#define OPT_ENTRY(L, N, T, S) { "setopt_"#L, lcurl_share_set_##N }, + #include "lcoptshare.h" +#undef OPT_ENTRY + + {"close", lcurl_share_cleanup }, + {"__gc", lcurl_share_cleanup }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_share_opt[] = { + +#define OPT_ENTRY(L, N, T, S) { "OPT_SHARE_"#N, CURLSHOPT_##N }, +#define FLG_ENTRY(N) { #N, CURL_##N }, +# include "lcoptshare.h" +#undef OPT_ENTRY +#undef FLG_ENTRY + + {NULL, 0} +}; + +void lcurl_share_initlib(lua_State *L, int nup){ + if(!lutil_createmetap(L, LCURL_SHARE, lcurl_share_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_share_opt); +} diff --git a/watchdog/third_party/lua-curl/src/lcshare.h b/watchdog/third_party/lua-curl/src/lcshare.h new file mode 100644 index 0000000..a1018c9 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcshare.h @@ -0,0 +1,30 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCSHARE_H_ +#define _LCSHARE_H_ + +#include "lcurl.h" +#include "lcutils.h" + +typedef struct lcurl_share_tag{ + CURLM *curl; + int err_mode; +}lcurl_share_t; + +int lcurl_share_create(lua_State *L, int error_mode); + +lcurl_share_t *lcurl_getshare_at(lua_State *L, int i); + +#define lcurl_getshare(L) lcurl_getshare_at((L),1) + +void lcurl_share_initlib(lua_State *L, int nup); + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcurl.c b/watchdog/third_party/lua-curl/src/lcurl.c new file mode 100644 index 0000000..e70680d --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcurl.c @@ -0,0 +1,487 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lceasy.h" +#include "lcmulti.h" +#include "lcshare.h" +#include "lcerror.h" +#include "lchttppost.h" +#include "lcmime.h" +#include "lcurlapi.h" +#include "lcutils.h" + +/*export*/ +#ifdef _WIN32 +# define LCURL_EXPORT_API __declspec(dllexport) +#else +# define LCURL_EXPORT_API LUALIB_API +#endif + +static const char* LCURL_REGISTRY = "LCURL Registry"; +static const char* LCURL_USERVAL = "LCURL Uservalues"; +#if LCURL_CURL_VER_GE(7,56,0) +static const char* LCURL_MIME_EASY_MAP = "LCURL Mime easy"; +#endif + +#if LCURL_CURL_VER_GE(7,56,0) +#define NUP 3 +#else +#define NUP 2 +#endif + +static volatile int LCURL_INIT = 0; + +static int lcurl_init_in_mode(lua_State *L, long init_mode, int error_mode){ + if(!LCURL_INIT){ + /* Note from libcurl documentation. + * + * The environment it sets up is constant for the life of the program + * and is the same for every program, so multiple calls have the same + * effect as one call. ... This function is not thread safe. + */ + CURLcode code = curl_global_init(init_mode); + if (code != CURLE_OK) { + return lcurl_fail_ex(L, error_mode, LCURL_ERROR_CURL, code); + } + LCURL_INIT = 1; + } + return 0; +} + +static int lcurl_init(lua_State *L, int error_mode){ + long init_mode = CURL_GLOBAL_DEFAULT; + if (L != NULL) { + int type = lua_type(L, 1); + if (type == LUA_TNUMBER) { + init_mode = lua_tonumber(L, 1); + } + } + return lcurl_init_in_mode(L, init_mode, error_mode); +} + +static int lcurl_init_default(lua_State *L){ + return lcurl_init_in_mode(L, CURL_GLOBAL_DEFAULT, LCURL_ERROR_RAISE); +} + +static int lcurl_init_unsafe(lua_State *L){ + return lcurl_init(L, LCURL_ERROR_RAISE); +} + +static int lcurl_init_safe(lua_State *L){ + return lcurl_init(L, LCURL_ERROR_RETURN); +} + +static int lcurl_easy_new_safe(lua_State *L){ + return lcurl_easy_create(L, LCURL_ERROR_RETURN); +} + +static int lcurl_multi_new_safe(lua_State *L){ + return lcurl_multi_create(L, LCURL_ERROR_RETURN); +} + +static int lcurl_share_new_safe(lua_State *L){ + return lcurl_share_create(L, LCURL_ERROR_RETURN); +} + +static int lcurl_hpost_new_safe(lua_State *L) { + return lcurl_hpost_create(L, LCURL_ERROR_RETURN); +} + +#if LCURL_CURL_VER_GE(7,62,0) + +static int lcurl_url_new_safe(lua_State *L) { + return lcurl_url_create(L, LCURL_ERROR_RETURN); +} + +#endif + +static int lcurl_easy_new(lua_State *L){ + return lcurl_easy_create(L, LCURL_ERROR_RAISE); +} + +static int lcurl_multi_new(lua_State *L){ + return lcurl_multi_create(L, LCURL_ERROR_RAISE); +} + +static int lcurl_share_new(lua_State *L){ + return lcurl_share_create(L, LCURL_ERROR_RAISE); +} + +static int lcurl_hpost_new(lua_State *L){ + return lcurl_hpost_create(L, LCURL_ERROR_RAISE); +} + +#if LCURL_CURL_VER_GE(7,62,0) + +static int lcurl_url_new(lua_State *L) { + return lcurl_url_create(L, LCURL_ERROR_RAISE); +} + +#endif + +#if LCURL_CURL_VER_GE(7,73,0) + +static void lcurl_easy_option_push(lua_State *L, const struct curl_easyoption *opt) { + lua_newtable(L); + lua_pushliteral(L, "id"); lutil_pushuint(L, opt->id); lua_rawset(L, -3); + lua_pushliteral(L, "name"); lua_pushstring(L, opt->name); lua_rawset(L, -3); + lua_pushliteral(L, "type"); lutil_pushuint(L, opt->type); lua_rawset(L, -3); + lua_pushliteral(L, "flags"); lutil_pushuint(L, opt->flags); lua_rawset(L, -3); + lua_pushliteral(L, "flags_set"); lua_newtable(L); + lua_pushliteral(L, "alias"); lua_pushboolean(L, opt->flags & CURLOT_FLAG_ALIAS); lua_rawset(L, -3); + lua_rawset(L, -3); + lua_pushliteral(L, "type_name"); + switch(opt->type){ + case CURLOT_LONG : lua_pushliteral(L, "LONG" ); break; + case CURLOT_VALUES : lua_pushliteral(L, "VALUES" ); break; + case CURLOT_OFF_T : lua_pushliteral(L, "OFF_T" ); break; + case CURLOT_OBJECT : lua_pushliteral(L, "OBJECT" ); break; + case CURLOT_STRING : lua_pushliteral(L, "STRING" ); break; + case CURLOT_SLIST : lua_pushliteral(L, "SLIST" ); break; + case CURLOT_CBPTR : lua_pushliteral(L, "CBPTR" ); break; + case CURLOT_BLOB : lua_pushliteral(L, "BLOB" ); break; + case CURLOT_FUNCTION: lua_pushliteral(L, "FUNCTION"); break; + default: lua_pushliteral(L, "UNKNOWN"); + } + lua_rawset(L, -3); +} + +static int lcurl_easy_option_next(lua_State *L) { + const struct curl_easyoption *opt; + + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 1); + + lua_rawgeti(L, 1, 1); + opt = lua_touserdata(L, -1); + lua_settop(L, 1); + + opt = curl_easy_option_next(opt); + if (!opt) { + return 0; + } + + lcurl_easy_option_push(L, opt); + + lua_pushlightuserdata(L, (void*)opt); + lua_rawseti(L, 1, 1); + + return 1; +} + +static int lcurl_easy_option_by_id(lua_State *L) { + const struct curl_easyoption *opt = NULL; + lua_Integer id = luaL_checkinteger(L, 1); + + lua_settop(L, 0); + opt = curl_easy_option_by_id(id); + if (!opt) { + return 0; + } + + lcurl_easy_option_push(L, opt); + + return 1; +} + +static int lcurl_easy_option_by_name(lua_State *L) { + const struct curl_easyoption *opt = NULL; + const char *name = luaL_checkstring(L, 1); + + lua_settop(L, 0); + opt = curl_easy_option_by_name(name); + if (!opt) { + return 0; + } + + lcurl_easy_option_push(L, opt); + + return 1; +} + +static int lcurl_easy_option_iter(lua_State *L) { + lua_pushcfunction(L, lcurl_easy_option_next); + lua_newtable(L); + return 2; +} + +#endif + +static int lcurl_version(lua_State *L){ + lua_pushstring(L, curl_version()); + return 1; +} + +static int lcurl_debug_getregistry(lua_State *L) { + lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); + return 1; +} + +static int push_upper(lua_State *L, const char *str){ + char buffer[128]; + size_t i, n = strlen(str); + char *ptr = (n < sizeof(buffer))?&buffer[0]:malloc(n + 1); + if (!ptr) return 1; + for(i = 0; i < n; ++i){ + if( (str[i] > 96 ) && (str[i] < 123) ) ptr[i] = str[i] - 'a' + 'A'; + else ptr[i] = str[i]; + } + lua_pushlstring(L, ptr, n); + if(ptr != &buffer[0]) free(ptr); + return 0; +} + +static int lcurl_version_info(lua_State *L){ + const char * const*p; + curl_version_info_data *data = curl_version_info(CURLVERSION_NOW); + + lua_newtable(L); + lua_pushstring(L, data->version); lua_setfield(L, -2, "version"); /* LIBCURL_VERSION */ + lutil_pushuint(L, data->version_num); lua_setfield(L, -2, "version_num"); /* LIBCURL_VERSION_NUM */ + lua_pushstring(L, data->host); lua_setfield(L, -2, "host"); /* OS/host/cpu/machine when configured */ + + lua_newtable(L); + lua_pushliteral(L, "IPV6"); lua_pushboolean(L, data->features & CURL_VERSION_IPV6 ); lua_rawset(L, -3); + lua_pushliteral(L, "KERBEROS4"); lua_pushboolean(L, data->features & CURL_VERSION_KERBEROS4 ); lua_rawset(L, -3); + lua_pushliteral(L, "SSL"); lua_pushboolean(L, data->features & CURL_VERSION_SSL ); lua_rawset(L, -3); + lua_pushliteral(L, "LIBZ"); lua_pushboolean(L, data->features & CURL_VERSION_LIBZ ); lua_rawset(L, -3); + lua_pushliteral(L, "NTLM"); lua_pushboolean(L, data->features & CURL_VERSION_NTLM ); lua_rawset(L, -3); + lua_pushliteral(L, "GSSNEGOTIATE"); lua_pushboolean(L, data->features & CURL_VERSION_GSSNEGOTIATE); lua_rawset(L, -3); +#if LCURL_CURL_VER_GE(7,38,0) + lua_pushliteral(L, "GSSAPI"); lua_pushboolean(L, data->features & CURL_VERSION_GSSAPI ); lua_rawset(L, -3); +#endif + lua_pushliteral(L, "DEBUG"); lua_pushboolean(L, data->features & CURL_VERSION_DEBUG ); lua_rawset(L, -3); + lua_pushliteral(L, "ASYNCHDNS"); lua_pushboolean(L, data->features & CURL_VERSION_ASYNCHDNS ); lua_rawset(L, -3); + lua_pushliteral(L, "SPNEGO"); lua_pushboolean(L, data->features & CURL_VERSION_SPNEGO ); lua_rawset(L, -3); + lua_pushliteral(L, "LARGEFILE"); lua_pushboolean(L, data->features & CURL_VERSION_LARGEFILE ); lua_rawset(L, -3); + lua_pushliteral(L, "IDN"); lua_pushboolean(L, data->features & CURL_VERSION_IDN ); lua_rawset(L, -3); + lua_pushliteral(L, "SSPI"); lua_pushboolean(L, data->features & CURL_VERSION_SSPI ); lua_rawset(L, -3); + lua_pushliteral(L, "CONV"); lua_pushboolean(L, data->features & CURL_VERSION_CONV ); lua_rawset(L, -3); + lua_pushliteral(L, "CURLDEBUG"); lua_pushboolean(L, data->features & CURL_VERSION_CURLDEBUG ); lua_rawset(L, -3); +#if LCURL_CURL_VER_GE(7,21,4) + lua_pushliteral(L, "TLSAUTH_SRP"); lua_pushboolean(L, data->features & CURL_VERSION_TLSAUTH_SRP ); lua_rawset(L, -3); +#endif +#if LCURL_CURL_VER_GE(7,22,0) + lua_pushliteral(L, "NTLM_WB"); lua_pushboolean(L, data->features & CURL_VERSION_NTLM_WB ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_HTTP2 + lua_pushliteral(L, "HTTP2"); lua_pushboolean(L, data->features & CURL_VERSION_HTTP2 ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_HTTPS_PROXY + lua_pushliteral(L, "HTTPS_PROXY"); lua_pushboolean(L, data->features & CURL_VERSION_HTTPS_PROXY ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_MULTI_SSL + lua_pushliteral(L, "MULTI_SSL"); lua_pushboolean(L, data->features & CURL_VERSION_MULTI_SSL ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_BROTLI + lua_pushliteral(L, "BROTLI"); lua_pushboolean(L, data->features & CURL_VERSION_BROTLI ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_ALTSVC + lua_pushliteral(L, "ALTSVC"); lua_pushboolean(L, data->features & CURL_VERSION_ALTSVC ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_HTTP3 + lua_pushliteral(L, "HTTP3"); lua_pushboolean(L, data->features & CURL_VERSION_HTTP3 ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_ZSTD + lua_pushliteral(L, "ZSTD"); lua_pushboolean(L, data->features & CURL_VERSION_ZSTD ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_UNICODE + lua_pushliteral(L, "UNICODE"); lua_pushboolean(L, data->features & CURL_VERSION_UNICODE ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_HSTS + lua_pushliteral(L, "HSTS"); lua_pushboolean(L, data->features & CURL_VERSION_HSTS ); lua_rawset(L, -3); +#endif + + lua_setfield(L, -2, "features"); /* bitmask, see defines below */ + + if(data->ssl_version){lua_pushstring(L, data->ssl_version); lua_setfield(L, -2, "ssl_version");} /* human readable string */ + lutil_pushuint(L, data->ssl_version_num); lua_setfield(L, -2, "ssl_version_num"); /* not used anymore, always 0 */ + if(data->libz_version){lua_pushstring(L, data->libz_version); lua_setfield(L, -2, "libz_version");} /* human readable string */ + + /* protocols is terminated by an entry with a NULL protoname */ + lua_newtable(L); + for(p = data->protocols; *p; ++p){ + push_upper(L, *p); lua_pushboolean(L, 1); lua_rawset(L, -3); + } + lua_setfield(L, -2, "protocols"); + + if(data->age >= CURLVERSION_SECOND){ + if(data->ares){lua_pushstring(L, data->ares); lua_setfield(L, -2, "ares");} + lutil_pushuint(L, data->ares_num); lua_setfield(L, -2, "ares_num"); + } + + if(data->age >= CURLVERSION_THIRD){ /* added in 7.12.0 */ + if(data->libidn){lua_pushstring(L, data->libidn); lua_setfield(L, -2, "libidn");} + } + +#if LCURL_CURL_VER_GE(7,16,1) + if(data->age >= CURLVERSION_FOURTH){ + lutil_pushuint(L, data->iconv_ver_num); lua_setfield(L, -2, "iconv_ver_num"); + if(data->libssh_version){lua_pushstring(L, data->libssh_version);lua_setfield(L, -2, "libssh_version");} + } +#endif + +#if LCURL_CURL_VER_GE(7,57,0) + if(data->age >= CURLVERSION_FOURTH){ + lutil_pushuint(L, data->brotli_ver_num); lua_setfield(L, -2, "brotli_ver_num"); + if(data->brotli_version){lua_pushstring(L, data->brotli_version);lua_setfield(L, -2, "brotli_version");} + } +#endif + +#if LCURL_CURL_VER_GE(7,66,0) + if(data->age >= CURLVERSION_SIXTH){ + lutil_pushuint(L, data->nghttp2_ver_num); lua_setfield(L, -2, "nghttp2_ver_num"); + if(data->nghttp2_version){lua_pushstring(L, data->nghttp2_version);lua_setfield(L, -2, "nghttp2_version");} + if(data->quic_version){lua_pushstring(L, data->quic_version);lua_setfield(L, -2, "quic_version");} + } +#endif + +#if LCURL_CURL_VER_GE(7,70,0) + if(data->age >= CURLVERSION_SEVENTH){ + if(data->cainfo){lua_pushstring(L, data->cainfo);lua_setfield(L, -2, "cainfo");} + if(data->capath){lua_pushstring(L, data->capath);lua_setfield(L, -2, "capath");} + } +#endif + +#if LCURL_CURL_VER_GE(7,72,0) + if(data->age >= CURLVERSION_EIGHTH){ + lutil_pushuint(L, data->zstd_ver_num); lua_setfield(L, -2, "zstd_ver_num"); + if(data->zstd_version){lua_pushstring(L, data->zstd_version);lua_setfield(L, -2, "zstd_version");} + } +#endif + + if(lua_isstring(L, 1)){ + lua_pushvalue(L, 1); lua_rawget(L, -2); + } + + return 1; +} + +static const struct luaL_Reg lcurl_functions[] = { + {"init", lcurl_init_unsafe }, + {"error", lcurl_error_new }, + {"form", lcurl_hpost_new }, + {"easy", lcurl_easy_new }, + {"multi", lcurl_multi_new }, + {"share", lcurl_share_new }, +#if LCURL_CURL_VER_GE(7,62,0) + {"url", lcurl_url_new }, +#endif + {"version", lcurl_version }, + {"version_info", lcurl_version_info }, +#if LCURL_CURL_VER_GE(7,73,0) + {"ieasy_options", lcurl_easy_option_iter }, + {"easy_option_by_id", lcurl_easy_option_by_id }, + {"easy_option_by_name", lcurl_easy_option_by_name }, +#endif + + {"__getregistry", lcurl_debug_getregistry}, + + {NULL,NULL} +}; + +static const struct luaL_Reg lcurl_functions_safe[] = { + {"init", lcurl_init_safe }, + {"error", lcurl_error_new }, + {"form", lcurl_hpost_new_safe }, + {"easy", lcurl_easy_new_safe }, + {"multi", lcurl_multi_new_safe }, + {"share", lcurl_share_new_safe }, +#if LCURL_CURL_VER_GE(7,62,0) + {"url", lcurl_url_new_safe }, +#endif + {"version", lcurl_version }, + {"version_info", lcurl_version_info }, +#if LCURL_CURL_VER_GE(7,73,0) + {"ieasy_options", lcurl_easy_option_iter }, + {"easy_option_by_id", lcurl_easy_option_by_id }, + {"easy_option_by_name", lcurl_easy_option_by_name }, +#endif + + { "__getregistry", lcurl_debug_getregistry }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_flags[] = { + +#define FLG_ENTRY(N) { #N, CURL##N }, +#include "lcflags.h" +#undef FLG_ENTRY + + {NULL, 0} +}; + +#if LCURL_CURL_VER_GE(7,56,0) +#define LCURL_PUSH_NUP(L) lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1); +#else +#define LCURL_PUSH_NUP(L) lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1); +#endif + +static int luaopen_lcurl_(lua_State *L, const struct luaL_Reg *func){ + if (getenv("LCURL_NO_INIT") == NULL) { // do not initialize curl if env variable LCURL_NO_INIT defined + lcurl_init_default(L); + } + + lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); + if(!lua_istable(L, -1)){ /* registry */ + lua_pop(L, 1); + lua_newtable(L); + } + + lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_USERVAL); + if(!lua_istable(L, -1)){ /* usevalues */ + lua_pop(L, 1); + lcurl_util_new_weak_table(L, "k"); + } + +#if LCURL_CURL_VER_GE(7,56,0) + lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_MIME_EASY_MAP); + if(!lua_istable(L, -1)){ /* Mime->Easy */ + lua_pop(L, 1); + lcurl_util_new_weak_table(L, "v"); + } +#endif + + lua_newtable(L); /* library */ + + LCURL_PUSH_NUP(L); luaL_setfuncs(L, func, NUP); + LCURL_PUSH_NUP(L); lcurl_error_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_hpost_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_easy_initlib (L, NUP); + LCURL_PUSH_NUP(L); lcurl_mime_initlib (L, NUP); + LCURL_PUSH_NUP(L); lcurl_multi_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_share_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_url_initlib (L, NUP); + + LCURL_PUSH_NUP(L); + +#if LCURL_CURL_VER_GE(7,56,0) + lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_MIME_EASY_MAP); +#endif + + lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_USERVAL); + lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); + + lcurl_util_set_const(L, lcurl_flags); + + lutil_push_null(L); + lua_setfield(L, -2, "null"); + + return 1; +} + +LCURL_EXPORT_API +int luaopen_lcurl(lua_State *L){ return luaopen_lcurl_(L, lcurl_functions); } + +LCURL_EXPORT_API +int luaopen_lcurl_safe(lua_State *L){ return luaopen_lcurl_(L, lcurl_functions_safe); } + diff --git a/watchdog/third_party/lua-curl/src/lcurl.h b/watchdog/third_party/lua-curl/src/lcurl.h new file mode 100644 index 0000000..8f43320 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcurl.h @@ -0,0 +1,31 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCURL_H_ +#define _LCURL_H_ + +#include "l52util.h" +#include "curl/curl.h" +#include "curl/easy.h" +#include "curl/multi.h" + +#include +#include + +#define LCURL_PREFIX "LcURL" + +#define LCURL_LUA_REGISTRY lua_upvalueindex(1) + +#define LCURL_USERVALUES lua_upvalueindex(2) + +/* only for `mime` API */ +#define LCURL_MIME_EASY lua_upvalueindex(3) + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcurlapi.c b/watchdog/third_party/lua-curl/src/lcurlapi.c new file mode 100644 index 0000000..2a8d503 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcurlapi.c @@ -0,0 +1,218 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurlapi.h" +#include "lcurl.h" +#include "lcerror.h" +#include "lcutils.h" +#include + +#define LCURL_URL_NAME LCURL_PREFIX" URL" +static const char *LCURL_URL = LCURL_URL_NAME; + +#if LCURL_CURL_VER_GE(7,62,0) + +#define lcurl_geturl(L) lcurl_geturl_at(L, 1) + +int lcurl_url_create(lua_State *L, int error_mode){ + lcurl_url_t *p; + + p = lutil_newudatap(L, lcurl_url_t, LCURL_URL); + + p->url = curl_url(); + if(!p->url) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, CURLUE_OUT_OF_MEMORY); + + p->err_mode = error_mode; + + if (lua_gettop(L) > 1) { + const char *url = luaL_checkstring(L, 1); + unsigned int flags = 0; + CURLUcode code; + + if (lua_gettop(L) > 2) { + flags = (unsigned int)lutil_optint64(L, 2, 0); + } + + code = curl_url_set(p->url, CURLUPART_URL, url, flags); + if (code != CURLUE_OK) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, code); + } + } + + return 1; +} + +lcurl_url_t *lcurl_geturl_at(lua_State *L, int i){ + lcurl_url_t *p = (lcurl_url_t *)lutil_checkudatap (L, i, LCURL_URL); + luaL_argcheck (L, p != NULL, 1, LCURL_URL_NAME" object expected"); + return p; +} + +static int lcurl_url_cleanup(lua_State *L){ + lcurl_url_t *p = lcurl_geturl(L); + + if (p->url){ + curl_url_cleanup(p->url); + p->url = NULL; + } + + return 0; +} + +static int lcurl_url_dup(lua_State *L) { + lcurl_url_t *r = lcurl_geturl(L); + lcurl_url_t *p = lutil_newudatap(L, lcurl_url_t, LCURL_URL); + + p->url = curl_url_dup(r->url); + if (!p->url) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, CURLUE_OUT_OF_MEMORY); + + p->err_mode = r->err_mode; + + return 1; +} + +static int lcurl_url_set(lua_State *L, CURLUPart what){ + lcurl_url_t *p = lcurl_geturl(L); + CURLUcode code; + const char *part; + unsigned int flags = 0; + + luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lutil_is_null(L, 2), 2, "string expected"); + + part = lua_tostring(L, 2); + flags = (unsigned int)lutil_optint64(L, 3, 0); + + code = curl_url_set(p->url, what, part, flags); + if (code != CURLUE_OK) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, code); + } + + lua_settop(L, 1); + return 1; +} + +static int lcurl_url_get(lua_State *L, CURLUPart what, CURLUcode empty) { + lcurl_url_t *p = lcurl_geturl(L); + CURLUcode code; + char *part = NULL; + unsigned int flags = 0; + + flags = (unsigned int)lutil_optint64(L, 2, 0); + + code = curl_url_get(p->url, what, &part, flags); + if (code != CURLUE_OK) { + if (part) { + curl_free(part); + part = NULL; + } + + if (code != empty) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, code); + } + } + + if (part == NULL) { + lutil_push_null(L); + } + else { + lua_pushstring(L, part); + curl_free(part); + } + + return 1; +} + +static int lcurl_url_to_s(lua_State *L) { + lcurl_url_t *p = lcurl_geturl(L); + char *part = NULL; + + CURLUcode code = curl_url_get(p->url, CURLUPART_URL, &part, 0); + + if (code != CURLUE_OK) { + if (part) { + curl_free(part); + } + + return lcurl_fail_ex(L, LCURL_ERROR_RAISE, LCURL_ERROR_URL, code); + } + + if (part == NULL) { + lua_pushliteral(L, ""); + } + else { + lua_pushstring(L, part); + curl_free(part); + } + + return 1; +} + +#define ENTRY_PART(N, S, E) static int lcurl_url_set_##N(lua_State *L){\ + return lcurl_url_set(L, CURL##S);\ +} +#define ENTRY_FLAG(S) + +#include "lcopturl.h" + +#undef ENTRY_PART +#undef ENTRY_FLAG + +#define ENTRY_PART(N, S, E) static int lcurl_url_get_##N(lua_State *L){\ + return lcurl_url_get(L, CURL##S, E);\ +} +#define ENTRY_FLAG(S) + +#include "lcopturl.h" + +#undef ENTRY_PART +#undef ENTRY_FLAG + +static const struct luaL_Reg lcurl_url_methods[] = { + #define ENTRY_PART(N, S, E) { "set_"#N, lcurl_url_set_##N }, + #define ENTRY_FLAG(S) + #include "lcopturl.h" + #undef ENTRY_PART + #undef ENTRY_FLAG + + #define ENTRY_PART(N, S, E) { "get_"#N, lcurl_url_get_##N }, + #define ENTRY_FLAG(S) + #include "lcopturl.h" + #undef ENTRY_PART + #undef ENTRY_FLAG + + { "dup", lcurl_url_dup }, + { "cleanup", lcurl_url_cleanup }, + { "__gc", lcurl_url_cleanup }, + { "__tostring", lcurl_url_to_s }, + + { NULL,NULL } +}; + +static const lcurl_const_t lcurl_url_opt[] = { + #define ENTRY_PART(N, S, E) { #S, CURL##S }, + #define ENTRY_FLAG(S) { "U_"#S, CURLU_##S }, + #include "lcopturl.h" + #undef ENTRY_PART + #undef ENTRY_FLAG + {NULL, 0} +}; +#endif + +void lcurl_url_initlib(lua_State *L, int nup){ +#if LCURL_CURL_VER_GE(7,62,0) + if(!lutil_createmetap(L, LCURL_URL, lcurl_url_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_url_opt); +#else + lua_pop(L, nup); +#endif +} diff --git a/watchdog/third_party/lua-curl/src/lcurlapi.h b/watchdog/third_party/lua-curl/src/lcurlapi.h new file mode 100644 index 0000000..4dd4672 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcurlapi.h @@ -0,0 +1,34 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LURL_H_ +#define _LURL_H_ + +#include "lcurl.h" +#include "lcutils.h" +#include + +void lcurl_url_initlib(lua_State *L, int nup); + +#if LCURL_CURL_VER_GE(7,62,0) + +typedef struct lcurl_url_tag { + CURLU *url; + + int err_mode; +}lcurl_url_t; + +int lcurl_url_create(lua_State *L, int error_mode); + +lcurl_url_t *lcurl_geturl_at(lua_State *L, int i); + +#endif + +#endif \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/src/lcutils.c b/watchdog/third_party/lua-curl/src/lcutils.c new file mode 100644 index 0000000..6a406b4 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcutils.c @@ -0,0 +1,408 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lcutils.h" +#include "lcerror.h" + +#define LCURL_STORAGE_SLIST 1 +#define LCURL_STORAGE_KV 2 + +int lcurl_storage_init(lua_State *L){ + lua_newtable(L); + return luaL_ref(L, LCURL_LUA_REGISTRY); +} + +void lcurl_storage_preserve_value(lua_State *L, int storage, int i){ + assert(i > 0 && i <= lua_gettop(L)); + luaL_checkany(L, i); + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_pushvalue(L, i); lua_pushboolean(L, 1); lua_rawset(L, -3); + lua_pop(L, 1); +} + +void lcurl_storage_remove_value(lua_State *L, int storage, int i){ + assert(i > 0 && i <= lua_gettop(L)); + luaL_checkany(L, i); + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_pushvalue(L, i); lua_pushnil(L); lua_rawset(L, -3); + lua_pop(L, 1); +} + +static void lcurl_storage_ensure_t(lua_State *L, int t){ + lua_rawgeti(L, -1, t); + if(!lua_istable(L, -1)){ + lua_pop(L, 1); + lua_newtable(L); + lua_pushvalue(L, -1); + lua_rawseti(L, -3, t); + } +} + +int lcurl_storage_preserve_slist(lua_State *L, int storage, struct curl_slist * list){ + int r; + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lcurl_storage_ensure_t(L, LCURL_STORAGE_SLIST); + lua_pushlightuserdata(L, list); + r = luaL_ref(L, -2); + lua_pop(L, 2); + return r; +} + +void lcurl_storage_preserve_iv(lua_State *L, int storage, int i, int v){ + v = lua_absindex(L, v); + + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lcurl_storage_ensure_t(L, LCURL_STORAGE_KV); + lua_pushvalue(L, v); + lua_rawseti(L, -2, i); + lua_pop(L, 2); +} + +void lcurl_storage_remove_i(lua_State *L, int storage, int i){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_rawgeti(L, -1, LCURL_STORAGE_KV); + if(lua_istable(L, -1)){ + lua_pushnil(L); + lua_rawseti(L, -2, i); + } + lua_pop(L, 2); +} + +void lcurl_storage_get_i(lua_State *L, int storage, int i){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_rawgeti(L, -1, LCURL_STORAGE_KV); + if(lua_istable(L, -1)){ + lua_rawgeti(L, -1, i); + lua_remove(L, -2); + } + lua_remove(L, -2); +} + +struct curl_slist* lcurl_storage_remove_slist(lua_State *L, int storage, int idx){ + struct curl_slist* list = NULL; + assert(idx != LUA_NOREF); + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_rawgeti(L, -1, LCURL_STORAGE_SLIST); // list storage + if(lua_istable(L, -1)){ + lua_rawgeti(L, -1, idx); + list = lua_touserdata(L, -1); + assert(list); + luaL_unref(L, -2, idx); + lua_pop(L, 1); + } + lua_pop(L, 2); + return list; +} + +int lcurl_storage_free(lua_State *L, int storage){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_rawgeti(L, -1, LCURL_STORAGE_SLIST); // list storage + if(lua_istable(L, -1)){ + lua_pushnil(L); + while(lua_next(L, -2) != 0){ + struct curl_slist * list = lua_touserdata(L, -1); + curl_slist_free_all(list); + lua_pushvalue(L, -2); lua_pushnil(L); + lua_rawset(L, -5); + lua_pop(L, 1); + } + } + luaL_unref(L, LCURL_LUA_REGISTRY, storage); + lua_pop(L, 2); + return LUA_NOREF; +} + +struct curl_slist* lcurl_util_array_to_slist(lua_State *L, int t){ + struct curl_slist *list = NULL; + int i, n = lua_rawlen(L, t); + + assert(lua_type(L, t) == LUA_TTABLE); + + for(i = 1; i <= n; ++i){ + lua_rawgeti(L, t, i); + list = curl_slist_append(list, lua_tostring(L, -1)); + lua_pop(L, 1); + } + return list; +} + +struct curl_slist* lcurl_util_to_slist(lua_State *L, int t){ + if(lua_type(L, t) == LUA_TTABLE){ + return lcurl_util_array_to_slist(L, t); + } + return 0; +} + +void lcurl_util_slist_set(lua_State *L, int t, struct curl_slist* list){ + int i; + t = lua_absindex(L, t); + for(i = 0;list;list = list->next){ + lua_pushstring(L, list->data); + lua_rawseti(L, t, ++i); + } +} + +void lcurl_util_slist_to_table(lua_State *L, struct curl_slist* list){ + lua_newtable(L); + lcurl_util_slist_set(L, -1, list); +} + +void lcurl_util_set_const(lua_State *L, const lcurl_const_t *reg){ + const lcurl_const_t *p; + for(p = reg; p->name; ++p){ + lua_pushstring(L, p->name); + lua_pushnumber(L, p->value); + lua_settable(L, -3); + } +} + +int lcurl_set_callback(lua_State *L, lcurl_callback_t *c, int i, const char *method){ + int top = lua_gettop(L); + i = lua_absindex(L, i); + + luaL_argcheck(L, !lua_isnoneornil(L, i), i, "no function present"); + luaL_argcheck(L, (top < (i + 2)), i + 2, "no arguments expected"); + + assert((top == i)||(top == (i + 1))); + + if(c->ud_ref != LUA_NOREF){ + luaL_unref(L, LCURL_LUA_REGISTRY, c->ud_ref); + c->ud_ref = LUA_NOREF; + } + + if(c->cb_ref != LUA_NOREF){ + luaL_unref(L, LCURL_LUA_REGISTRY, c->cb_ref); + c->cb_ref = LUA_NOREF; + } + + if(lutil_is_null(L, i)){ + if(top == (i + 1)){ + // Do we can just ignore this? + luaL_argcheck(L, + lua_isnoneornil(L, i + 1) || lutil_is_null(L, i + 1) + ,i + 1, "no context allowed when set callback to null" + ); + } + lua_pop(L, top - i + 1); + + return 1; + } + + if(lua_gettop(L) == (i + 1)){// function + context + c->ud_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + c->cb_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + + assert(top == (2 + lua_gettop(L))); + return 1; + } + + assert(top == i); + + if(lua_isfunction(L, i)){ // function + c->cb_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + + assert(top == (1 + lua_gettop(L))); + return 1; + } + + if(lua_isuserdata(L, i) || lua_istable(L, i)){ // object + lua_getfield(L, i, method); + + luaL_argcheck(L, lua_isfunction(L, -1), 2, "method not found in object"); + + c->cb_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + c->ud_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + + assert(top == (1 + lua_gettop(L))); + return 1; + } + + lua_pushliteral(L, "invalid object type"); + return lua_error(L); +} + +int lcurl_util_push_cb(lua_State *L, lcurl_callback_t *c){ + assert(c->cb_ref != LUA_NOREF); + lua_rawgeti(L, LCURL_LUA_REGISTRY, c->cb_ref); + if(c->ud_ref != LUA_NOREF){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, c->ud_ref); + return 2; + } + return 1; +} + +int lcurl_util_new_weak_table(lua_State*L, const char *mode){ + int top = lua_gettop(L); + lua_newtable(L); + lua_newtable(L); + lua_pushstring(L, mode); + lua_setfield(L, -2, "__mode"); + lua_setmetatable(L,-2); + assert((top+1) == lua_gettop(L)); + return 1; +} + +int lcurl_util_pcall_method(lua_State *L, const char *name, int nargs, int nresults, int errfunc){ + int obj_index = -nargs - 1; + lua_getfield(L, obj_index, name); + lua_insert(L, obj_index - 1); + return lua_pcall(L, nargs + 1, nresults, errfunc); +} + +static void lcurl_utils_pcall_close(lua_State *L, int obj){ + int top = lua_gettop(L); + lua_pushvalue(L, obj); + lcurl_util_pcall_method(L, "close", 0, 0, 0); + lua_settop(L, top); +} + +int lcurl_utils_apply_options(lua_State *L, int opt, int obj, int do_close, + int error_mode, int error_type, int error_code +){ + int top = lua_gettop(L); + opt = lua_absindex(L, opt); + obj = lua_absindex(L, obj); + + lua_pushnil(L); + while(lua_next(L, opt) != 0){ + int n; + assert(lua_gettop(L) == (top + 2)); + + if(lua_type(L, -2) == LUA_TNUMBER){ /* [curl.OPT_URL] = "http://localhost" */ + lua_pushvalue(L, -2); + lua_insert(L, -2); /*Stack : opt, obj, k, k, v */ + lua_pushliteral(L, "setopt"); /*Stack : opt, obj, k, k, v, "setopt" */ + n = 2; + } + else if(lua_type(L, -2) == LUA_TSTRING){ /* url = "http://localhost" */ + lua_pushliteral(L, "setopt_"); lua_pushvalue(L, -3); lua_concat(L, 2); + /*Stack : opt, obj, k, v, "setopt_XXX" */ + n = 1; + } + else{ + lua_pop(L, 1); + continue; + } + /*Stack : opt, obj, k,[ k,] v, `setoptXXX` */ + + lua_gettable(L, obj); /* get e["settop_XXX]*/ + + if(lua_isnil(L, -1)){ /* unknown option */ + if(do_close) lcurl_utils_pcall_close(L, obj); + lua_settop(L, top); + return lcurl_fail_ex(L, error_mode, error_type, error_code); + } + + lua_insert(L, -n-1); /*Stack : opt, obj, k, setoptXXX, [ k,] v */ + lua_pushvalue(L, obj); /*Stack : opt, obj, k, setoptXXX, [ k,] v, obj */ + lua_insert(L, -n-1); /*Stack : opt, obj, k, setoptXXX, obj, [ k,] v */ + + if(lua_pcall(L, n+1, 2, 0)){ + if(do_close) lcurl_utils_pcall_close(L, obj); + return lua_error(L); + } + + if(lua_isnil(L, -2)){ + if(do_close) lcurl_utils_pcall_close(L, obj); + lua_settop(L, top); + return 2; + } + + /*Stack : opt, obj, k, ok, nil*/ + lua_pop(L, 2); + assert(lua_gettop(L) == (top+1)); + } + assert(lua_gettop(L) == top); + return 0; +} + +void lcurl_stack_dump (lua_State *L){ + int i = 1, top = lua_gettop(L); + + fprintf(stderr, " ---------------- Stack Dump ----------------\n" ); + while( i <= top ) { + int t = lua_type(L, i); + switch (t) { + case LUA_TSTRING: + fprintf(stderr, "%d(%d):`%s'\n", i, i - top - 1, lua_tostring(L, i)); + break; + case LUA_TBOOLEAN: + fprintf(stderr, "%d(%d): %s\n", i, i - top - 1,lua_toboolean(L, i) ? "true" : "false"); + break; + case LUA_TNUMBER: + fprintf(stderr, "%d(%d): %g\n", i, i - top - 1, lua_tonumber(L, i)); + break; + default: + lua_getglobal(L, "tostring"); + lua_pushvalue(L, i); + lua_call(L, 1, 1); + fprintf(stderr, "%d(%d): %s(%s)\n", i, i - top - 1, lua_typename(L, t), lua_tostring(L, -1)); + lua_pop(L, 1); + break; + } + i++; + } + fprintf(stderr, " ------------ Stack Dump Finished ------------\n" ); +} + +curl_socket_t lcurl_opt_os_socket(lua_State *L, int idx, curl_socket_t def) { + if (lua_islightuserdata(L, idx)) + return (curl_socket_t)lua_touserdata(L, idx); + + return (curl_socket_t)lutil_optint64(L, idx, def); +} + +void lcurl_push_os_socket(lua_State *L, curl_socket_t fd) { +#if !defined(_WIN32) + lutil_pushint64(L, fd); +#else /*_WIN32*/ + /* Assumes that compiler can optimize constant conditions. MSVC do this. */ + + /*On Lua 5.3 lua_Integer type can be represented exactly*/ +#if LUA_VERSION_NUM >= 503 + if (sizeof(curl_socket_t) <= sizeof(lua_Integer)) { + lua_pushinteger(L, (lua_Integer)fd); + return; + } +#endif + +#if defined(LUA_NUMBER_DOUBLE) || defined(LUA_NUMBER_FLOAT) + /*! @todo test DBL_MANT_DIG, FLT_MANT_DIG */ + + if (sizeof(lua_Number) == 8) { /*we have 53 bits for integer*/ + if ((sizeof(curl_socket_t) <= 6)) { + lua_pushnumber(L, (lua_Number)fd); + return; + } + + if(((UINT_PTR)fd & 0x1FFFFFFFFFFFFF) == (UINT_PTR)fd) + lua_pushnumber(L, (lua_Number)fd); + else + lua_pushlightuserdata(L, (void*)fd); + + return; + } + + if (sizeof(lua_Number) == 4) { /*we have 24 bits for integer*/ + if (((UINT_PTR)fd & 0xFFFFFF) == (UINT_PTR)fd) + lua_pushnumber(L, (lua_Number)fd); + else + lua_pushlightuserdata(L, (void*)fd); + return; + } +#endif + + lutil_pushint64(L, fd); + if (lcurl_opt_os_socket(L, -1, 0) != fd) + lua_pushlightuserdata(L, (void*)fd); + +#endif /*_WIN32*/ +} diff --git a/watchdog/third_party/lua-curl/src/lcutils.h b/watchdog/third_party/lua-curl/src/lcutils.h new file mode 100644 index 0000000..cfad1af --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcutils.h @@ -0,0 +1,108 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCUTILS_H_ +#define _LCUTILS_H_ + +#include "lcurl.h" + +#if defined(_MSC_VER) || defined(__cplusplus) +# define LCURL_CC_SUPPORT_FORWARD_TYPEDEF 1 +#elif defined(__STDC_VERSION__) +# if __STDC_VERSION__ >= 201112 +# define LCURL_CC_SUPPORT_FORWARD_TYPEDEF 1 +# endif +#endif + +#ifndef LCURL_CC_SUPPORT_FORWARD_TYPEDEF +# define LCURL_CC_SUPPORT_FORWARD_TYPEDEF 0 +#endif + +#ifdef __GNUC__ + #define LCURL_UNUSED_TYPEDEF __attribute__ ((unused)) +#else + #define LCURL_UNUSED_TYPEDEF +#endif + +#define LCURL_UNUSED_VAR LCURL_UNUSED_TYPEDEF + +#define LCURL_MAKE_VERSION(MIN, MAJ, PAT) ((MIN<<16) + (MAJ<<8) + PAT) +#define LCURL_CURL_VER_GE(MIN, MAJ, PAT) (LIBCURL_VERSION_NUM >= LCURL_MAKE_VERSION(MIN, MAJ, PAT)) + +#define LCURL_CONCAT_STATIC_ASSERT_IMPL_(x, y) LCURL_CONCAT1_STATIC_ASSERT_IMPL_ (x, y) +#define LCURL_CONCAT1_STATIC_ASSERT_IMPL_(x, y) LCURL_UNUSED_TYPEDEF x##y +#define LCURL_STATIC_ASSERT(expr) typedef char LCURL_CONCAT_STATIC_ASSERT_IMPL_(static_assert_failed_at_line_, __LINE__) [(expr) ? 1 : -1] + +#define LCURL_ASSERT_SAME_SIZE(a, b) LCURL_STATIC_ASSERT( sizeof(a) == sizeof(b) ) +#define LCURL_ASSERT_SAME_OFFSET(a, am, b, bm) LCURL_STATIC_ASSERT( (offsetof(a,am)) == (offsetof(b,bm)) ) +#define LCURL_ASSERT_SAME_FIELD_SIZE(a, am, b, bm) LCURL_ASSERT_SAME_SIZE(((a*)0)->am, ((b*)0)->bm) + +typedef struct lcurl_const_tag{ + const char *name; + long value; +}lcurl_const_t; + +typedef struct lcurl_callback_tag{ + int cb_ref; + int ud_ref; +}lcurl_callback_t; + +typedef struct lcurl_read_buffer_tag{ + int ref; + size_t off; +}lcurl_read_buffer_t; + +int lcurl_storage_init(lua_State *L); + +void lcurl_storage_preserve_value(lua_State *L, int storage, int i); + +void lcurl_storage_remove_value(lua_State *L, int storage, int i); + +int lcurl_storage_preserve_slist(lua_State *L, int storage, struct curl_slist * list); + +struct curl_slist* lcurl_storage_remove_slist(lua_State *L, int storage, int idx); + +void lcurl_storage_preserve_iv(lua_State *L, int storage, int i, int v); + +void lcurl_storage_remove_i(lua_State *L, int storage, int i); + +void lcurl_storage_get_i(lua_State *L, int storage, int i); + +int lcurl_storage_free(lua_State *L, int storage); + +struct curl_slist* lcurl_util_array_to_slist(lua_State *L, int t); + +struct curl_slist* lcurl_util_to_slist(lua_State *L, int t); + +void lcurl_util_slist_set(lua_State *L, int t, struct curl_slist* list); + +void lcurl_util_slist_to_table(lua_State *L, struct curl_slist* list); + +void lcurl_util_set_const(lua_State *L, const lcurl_const_t *reg); + +int lcurl_set_callback(lua_State *L, lcurl_callback_t *c, int i, const char *method); + +int lcurl_util_push_cb(lua_State *L, lcurl_callback_t *c); + +int lcurl_util_new_weak_table(lua_State*L, const char *mode); + +int lcurl_util_pcall_method(lua_State *L, const char *name, int nargs, int nresults, int errfunc); + +int lcurl_utils_apply_options(lua_State *L, int opt, int obj, int do_close, + int error_mode, int error_type, int error_code + ); + +void lcurl_stack_dump (lua_State *L); + +curl_socket_t lcurl_opt_os_socket(lua_State *L, int idx, curl_socket_t def); + +void lcurl_push_os_socket(lua_State *L, curl_socket_t fd); + +#endif