aboutsummaryrefslogtreecommitdiff
path: root/src/ledger.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ledger.c')
-rw-r--r--src/ledger.c171
1 files changed, 171 insertions, 0 deletions
diff --git a/src/ledger.c b/src/ledger.c
index 72402f0..4dacde4 100644
--- a/src/ledger.c
+++ b/src/ledger.c
@@ -66,6 +66,177 @@ void ledger_voucher_hash(const unsigned char prev[32], int64_t org_id,
buf_free(&b);
}
+static void verify_err(char **err, const char *msg)
+{
+ if (err && !*err)
+ *err = xstrdup(msg);
+}
+
+static char *copy_text(const unsigned char *p)
+{
+ return xstrdup(p ? (const char *)p : "");
+}
+
+static void free_rows(struct ledger_row *rows, size_t nrows)
+{
+ for (size_t i = 0; i < nrows; i++) {
+ free((char *)rows[i].account);
+ free((char *)rows[i].description);
+ }
+ free(rows);
+}
+
+static int verify_org(sqlite3 *db, int64_t org_id,
+ struct ledger_verify_result *out, char **err)
+{
+ sqlite3_stmt *vs = NULL, *rs = NULL;
+ if (sqlite3_prepare_v2(
+ db,
+ "SELECT v.id,v.hash_prev,v.hash,fy.label,v.series,v.number,v.date,"
+ "v.description FROM vouchers v JOIN fiscal_years fy"
+ " ON fy.org_id=v.org_id AND fy.id=v.fiscal_year_id"
+ " WHERE v.org_id=?1 ORDER BY v.id",
+ -1, &vs, NULL) != SQLITE_OK) {
+ verify_err(err, sqlite3_errmsg(db));
+ return -1;
+ }
+ if (sqlite3_prepare_v2(
+ db,
+ "SELECT a.number,r.debit_ore,r.credit_ore,r.description"
+ " FROM voucher_rows r JOIN accounts a"
+ " ON a.org_id=r.org_id AND a.id=r.account_id"
+ " WHERE r.org_id=?1 AND r.voucher_id=?2 ORDER BY r.line_no",
+ -1, &rs, NULL) != SQLITE_OK) {
+ sqlite3_finalize(vs);
+ verify_err(err, sqlite3_errmsg(db));
+ return -1;
+ }
+
+ unsigned char running[32];
+ memset(running, 0, sizeof running);
+ int ret = 0;
+ sqlite3_bind_int64(vs, 1, org_id);
+ for (;;) {
+ int step = sqlite3_step(vs);
+ if (step == SQLITE_DONE)
+ break;
+ if (step != SQLITE_ROW) {
+ verify_err(err, sqlite3_errmsg(db));
+ ret = -1;
+ break;
+ }
+ int64_t id = sqlite3_column_int64(vs, 0);
+ const void *hprev = sqlite3_column_blob(vs, 1);
+ int hprev_n = sqlite3_column_bytes(vs, 1);
+ const void *hstored = sqlite3_column_blob(vs, 2);
+ int hstored_n = sqlite3_column_bytes(vs, 2);
+ char *label = copy_text(sqlite3_column_text(vs, 3));
+ char *series = copy_text(sqlite3_column_text(vs, 4));
+ int64_t number = sqlite3_column_int64(vs, 5);
+ char *date = copy_text(sqlite3_column_text(vs, 6));
+ char *vdesc = copy_text(sqlite3_column_text(vs, 7));
+
+ struct ledger_row *rows = NULL;
+ size_t nrows = 0, cap = 0;
+ int64_t sum_debit = 0, sum_credit = 0;
+ sqlite3_bind_int64(rs, 1, org_id);
+ sqlite3_bind_int64(rs, 2, id);
+ int rstep;
+ while ((rstep = sqlite3_step(rs)) == SQLITE_ROW) {
+ if (nrows == cap) {
+ cap = cap ? cap * 2 : 8;
+ rows = xrealloc(rows, cap * sizeof *rows);
+ }
+ rows[nrows].account = copy_text(sqlite3_column_text(rs, 0));
+ rows[nrows].debit_ore = sqlite3_column_int64(rs, 1);
+ rows[nrows].credit_ore = sqlite3_column_int64(rs, 2);
+ rows[nrows].description = copy_text(sqlite3_column_text(rs, 3));
+ sum_debit += rows[nrows].debit_ore;
+ sum_credit += rows[nrows].credit_ore;
+ nrows++;
+ }
+ sqlite3_reset(rs);
+ sqlite3_clear_bindings(rs);
+ if (rstep != SQLITE_DONE) {
+ verify_err(err, sqlite3_errmsg(db));
+ free_rows(rows, nrows);
+ free(label);
+ free(series);
+ free(date);
+ free(vdesc);
+ ret = -1;
+ break;
+ }
+
+ unsigned char expect[32];
+ ledger_voucher_hash(running, org_id, label, series, number, date, vdesc,
+ rows, nrows, expect);
+ out->vouchers_checked++;
+ if (sum_debit != sum_credit) {
+ out->unbalanced_vouchers++;
+ if (!out->first_unbalanced_voucher_id)
+ out->first_unbalanced_voucher_id = id;
+ if (ret == 0)
+ ret = 2;
+ }
+ if (hprev_n != 32 || hstored_n != 32 ||
+ memcmp(hprev, running, 32) != 0 ||
+ memcmp(hstored, expect, 32) != 0) {
+ out->first_bad_voucher_id = id;
+ ret = 1;
+ } else {
+ memcpy(running, expect, 32);
+ }
+ free_rows(rows, nrows);
+ free(label);
+ free(series);
+ free(date);
+ free(vdesc);
+ if (ret == 1)
+ break;
+ }
+ sqlite3_finalize(vs);
+ sqlite3_finalize(rs);
+ return ret;
+}
+
+int ledger_verify(sqlite3 *db, struct ledger_verify_result *out, char **err)
+{
+ memset(out, 0, sizeof *out);
+ sqlite3_stmt *st = NULL;
+ if (sqlite3_prepare_v2(db, "SELECT DISTINCT org_id FROM vouchers"
+ " ORDER BY org_id",
+ -1, &st, NULL) != SQLITE_OK) {
+ verify_err(err, sqlite3_errmsg(db));
+ return -1;
+ }
+ int rc = 0;
+ for (;;) {
+ int step = sqlite3_step(st);
+ if (step == SQLITE_DONE)
+ break;
+ if (step != SQLITE_ROW) {
+ verify_err(err, sqlite3_errmsg(db));
+ rc = -1;
+ break;
+ }
+ int64_t org_id = sqlite3_column_int64(st, 0);
+ int r = verify_org(db, org_id, out, err);
+ if (r < 0) {
+ rc = -1;
+ break;
+ }
+ if (r == 1) {
+ rc = 1;
+ break;
+ }
+ if (r == 2)
+ rc = 1;
+ }
+ sqlite3_finalize(st);
+ return rc;
+}
+
static int idempotent_lookup(sqlite3 *db, int64_t org_id, const char *client_ref,
char **out_json)
{