/* * nanobbs, a tiny forums software. * Copyright (C) 2025-2026 Xavier Del Campo Romero * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "op.h" #include #include #include #include #include struct op { sqlite3_stmt *stmt; struct op_cfg cfg; }; static int teardown(struct op *const op) { int ret = 0, error; if (!op) return 0; else if ((error = sqlite3_finalize(op->stmt)) != SQLITE_OK) { fprintf(stderr, "%s: sqlite3_finalize: %s\n", __func__, sqlite3_errstr(error)); ret = -1; } const struct op_cfg *const cfg = &op->cfg; if (cfg->error) cfg->error(cfg->args); free(op); return ret; } static void run_error(void *const p) { teardown(p); } static int run(const struct http_payload *const pl, struct http_response *const r, void *const user, void *const args) { struct op *const op = args; sqlite3_stmt *const stmt = op->stmt; const struct op_cfg *const cfg = &op->cfg; int ret = -1, error = sqlite3_step(stmt); switch (error) { case SQLITE_BUSY: break; case SQLITE_ROW: if (!cfg->row) { fprintf(stderr, "%s: expected row callback\n", __func__); goto failure; } else if ((ret = cfg->row(op->stmt, pl, r, user, cfg->args))) goto failure; break; case SQLITE_CONSTRAINT: sqlite3_reset(op->stmt); if (!cfg->constraint) { fprintf(stderr, "%s: expected constraint callback\n", __func__); goto failure; } else if ((ret = cfg->constraint(cfg->args))) goto failure; /* Fall through. */ case SQLITE_DONE: { const int error = sqlite3_finalize(op->stmt); if (error != SQLITE_OK) { fprintf(stderr, "%s: sqlite3_finalize: %s\n", __func__, sqlite3_errstr(error)); ret = -1; goto failure; } else if (cfg->end && (ret = cfg->end(pl, r, user, cfg->args))) goto failure; free(op); } break; default: fprintf(stderr, "%s: sqlite3_step: %s\n", __func__, sqlite3_errstr(error)); sqlite3_reset(stmt); goto failure; } return 0; failure: if (teardown(op)) { fprintf(stderr, "%s: teardown failed\n", __func__); ret = -1; } return ret; } void op_resume(struct op *const op, struct http_response *const r) { *r = (const struct http_response) { .step.payload = run, .free = run_error, .args = op }; } struct op *op_run(sqlite3 *const db, const char *const query, const struct op_cfg *const cfg, struct http_response *const r) { sqlite3_stmt *stmt = NULL; struct op *const ret = malloc(sizeof *ret); if (!ret) { fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); goto failure; } const int error = sqlite3_prepare_v2(db, query, -1, &stmt, NULL); if (error != SQLITE_OK && error != SQLITE_BUSY) { fprintf(stderr, "%s: sqlite3_prepare_v2 \"%s\": %s\n", __func__, query, sqlite3_errstr(error)); goto failure; } *r = (const struct http_response) { .step.payload = run, .free = run_error, .args = ret }; *ret = (const struct op) { .stmt = stmt, .cfg = *cfg }; return ret; failure: free(ret); sqlite3_finalize(stmt); return NULL; }