diff options
Diffstat (limited to 'src/ledger.c')
| -rw-r--r-- | src/ledger.c | 171 |
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) { |
