Mercurial > paster
changeset 84:94dcca86e5cc
pasterd: database reentrancy + timer
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 17 Mar 2023 20:01:00 +0100 |
parents | a55c0d7ff8fa |
children | 478d96a4b039 |
files | database.c database.h page-download.c page-fork.c page-index.c page-new.c page-paste.c page-search.c pasterd.c |
diffstat | 9 files changed, 117 insertions(+), 57 deletions(-) [+] |
line wrap: on
line diff
--- a/database.c Fri Mar 17 10:49:24 2023 +0100 +++ b/database.c Fri Mar 17 20:01:00 2023 +0100 @@ -37,8 +37,6 @@ #define CHAR(sql) (const char *)(sql) -static sqlite3 *db; - static char * dup(const unsigned char *s) { @@ -59,14 +57,14 @@ } static int -exists(const char *id) +exists(struct database *db, const char *id) { assert(id); sqlite3_stmt *stmt = NULL; int ret = 1; - if (sqlite3_prepare(db, CHAR(sql_get), -1, &stmt, NULL) == SQLITE_OK) { + if (sqlite3_prepare(db->handle, CHAR(sql_get), -1, &stmt, NULL) == SQLITE_OK) { sqlite3_bind_text(stmt, 1, id, -1, NULL); ret = sqlite3_step(stmt) == SQLITE_ROW; sqlite3_finalize(stmt); @@ -88,7 +86,7 @@ } static int -set_id(struct paste *paste) +set_id(struct database *db, struct paste *paste) { assert(paste); @@ -105,28 +103,31 @@ do { free(paste->id); paste->id = estrdup(create_id()); - } while (++tries < 30 && exists(paste->id)); + } while (++tries < 30 && exists(db, paste->id)); return tries < 30 ? 0 : -1; } +struct database database; + int -database_open(const char *path) +database_open(struct database *db, const char *path) { + assert(db); assert(path); log_info("database: opening %s", path); - if (sqlite3_open(path, &db) != SQLITE_OK) { - log_warn("database: unable to open %s: %s", path, sqlite3_errmsg(db)); + if (sqlite3_open(path, (sqlite3 **)&db->handle) != SQLITE_OK) { + log_warn("database: unable to open %s: %s", path, sqlite3_errmsg(db->handle)); return -1; } /* Wait for 30 seconds to lock the database. */ - sqlite3_busy_timeout(db, 30000); + sqlite3_busy_timeout(db->handle, 30000); - if (sqlite3_exec(db, CHAR(sql_init), NULL, NULL, NULL) != SQLITE_OK) { - log_warn("database: unable to initialize %s: %s", path, sqlite3_errmsg(db)); + if (sqlite3_exec(db->handle, CHAR(sql_init), NULL, NULL, NULL) != SQLITE_OK) { + log_warn("database: unable to initialize %s: %s", path, sqlite3_errmsg(db->handle)); return -1; } @@ -134,8 +135,9 @@ } int -database_recents(struct paste *pastes, size_t *max) +database_recents(struct database *db, struct paste *pastes, size_t *max) { + assert(db); assert(pastes); assert(max); @@ -145,7 +147,7 @@ memset(pastes, 0, *max * sizeof (struct paste)); log_debug("database: accessing most recents"); - if (sqlite3_prepare(db, CHAR(sql_recents), -1, &stmt, NULL) != SQLITE_OK || + if (sqlite3_prepare(db->handle, CHAR(sql_recents), -1, &stmt, NULL) != SQLITE_OK || sqlite3_bind_int64(stmt, 1, *max) != SQLITE_OK) goto sqlite_err; @@ -159,7 +161,7 @@ return 0; sqlite_err: - log_warn("database: error (recents): %s\n", sqlite3_errmsg(db)); + log_warn("database: error (recents): %s\n", sqlite3_errmsg(db->handle)); if (stmt) sqlite3_finalize(stmt); @@ -170,8 +172,9 @@ } int -database_get(struct paste *paste, const char *id) +database_get(struct database *db, struct paste *paste, const char *id) { + assert(db); assert(paste); assert(id); @@ -181,7 +184,7 @@ memset(paste, 0, sizeof (struct paste)); log_debug("database: accessing paste with id: %s", id); - if (sqlite3_prepare(db, CHAR(sql_get), -1, &stmt, NULL) != SQLITE_OK || + if (sqlite3_prepare(db->handle, CHAR(sql_get), -1, &stmt, NULL) != SQLITE_OK || sqlite3_bind_text(stmt, 1, id, -1, NULL) != SQLITE_OK) goto sqlite_err; @@ -205,30 +208,31 @@ if (stmt) sqlite3_finalize(stmt); - log_warn("database: error (get): %s", sqlite3_errmsg(db)); + log_warn("database: error (get): %s", sqlite3_errmsg(db->handle)); return -1; } int -database_insert(struct paste *paste) +database_insert(struct database *db, struct paste *paste) { + assert(db); assert(paste); sqlite3_stmt *stmt = NULL; log_debug("database: creating new paste"); - if (sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { - log_warn("database: could not lock database: %s", sqlite3_errmsg(db)); + if (sqlite3_exec(db->handle, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { + log_warn("database: could not lock database: %s", sqlite3_errmsg(db->handle)); return -1; } - if (set_id(paste) < 0) { + if (set_id(db, paste) < 0) { log_warn("database: unable to randomize unique identifier"); - sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL); + sqlite3_exec(db->handle, "END TRANSACTION", NULL, NULL, NULL); return -1; } - if (sqlite3_prepare(db, CHAR(sql_insert), -1, &stmt, NULL) != SQLITE_OK) + if (sqlite3_prepare(db->handle, CHAR(sql_insert), -1, &stmt, NULL) != SQLITE_OK) goto sqlite_err; sqlite3_bind_text(stmt, 1, paste->id, -1, SQLITE_STATIC); @@ -242,7 +246,7 @@ if (sqlite3_step(stmt) != SQLITE_DONE) goto sqlite_err; - sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); + sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL); sqlite3_finalize(stmt); log_info("database: new paste (%s) from %s expires in one %lld seconds", @@ -251,8 +255,8 @@ return 0; sqlite_err: - log_warn("database: error (insert): %s", sqlite3_errmsg(db)); - sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL); + log_warn("database: error (insert): %s", sqlite3_errmsg(db->handle)); + sqlite3_exec(db->handle, "ROLLBACK", NULL, NULL, NULL); if (stmt) sqlite3_finalize(stmt); @@ -264,12 +268,14 @@ } int -database_search(struct paste *pastes, +database_search(struct database *db, + struct paste *pastes, size_t *max, const char *title, const char *author, const char *language) { + assert(db); assert(pastes); assert(max); @@ -288,7 +294,7 @@ author = author ? author : "%"; language = language ? language : "%"; - if (sqlite3_prepare(db, CHAR(sql_search), -1, &stmt, NULL) != SQLITE_OK) + if (sqlite3_prepare(db->handle, CHAR(sql_search), -1, &stmt, NULL) != SQLITE_OK) goto sqlite_err; if (sqlite3_bind_text(stmt, 1, title, -1, NULL) != SQLITE_OK) goto sqlite_err; @@ -309,7 +315,7 @@ return 0; sqlite_err: - log_warn("database: error (search): %s\n", sqlite3_errmsg(db)); + log_warn("database: error (search): %s\n", sqlite3_errmsg(db->handle)); if (stmt) sqlite3_finalize(stmt); @@ -320,21 +326,22 @@ } void -database_clear(void) +database_clear(struct database *db) { + assert(db); + log_debug("database: clearing deprecated pastes"); - if (sqlite3_exec(db, CHAR(sql_clear), NULL, NULL, NULL) != SQLITE_OK) - log_warn("database: error (clear): %s\n", sqlite3_errmsg(db)); + if (sqlite3_exec(db->handle, CHAR(sql_clear), NULL, NULL, NULL) != SQLITE_OK) + log_warn("database: error (clear): %s\n", sqlite3_errmsg(db->handle)); } void -database_finish(void) +database_finish(struct database *db) { - log_debug("database: closing"); + assert(db); - if (db) { - sqlite3_close(db); - db = NULL; - } + log_debug("database: closing"); + sqlite3_close(db->handle); + db->handle = NULL; }
--- a/database.h Fri Mar 17 10:49:24 2023 +0100 +++ b/database.h Fri Mar 17 20:01:00 2023 +0100 @@ -23,29 +23,41 @@ struct paste; -int -database_open(const char *); +struct database { + void *handle; +}; -int -database_recents(struct paste *, size_t *); +/** + * Global database handle for convenience. + * + * Should be initialized on startup. + */ +extern struct database database; int -database_get(struct paste *, const char *); +database_open(struct database *, const char *); + +int +database_recents(struct database *, struct paste *, size_t *); int -database_insert(struct paste *); +database_get(struct database *, struct paste *, const char *); int -database_search(struct paste *, +database_insert(struct database *, struct paste *); + +int +database_search(struct database *, + struct paste *, size_t *, const char *, const char *, const char *); void -database_clear(void); +database_clear(struct database *); void -database_finish(void); +database_finish(struct database *); #endif /* !PASTER_DATABASE_H */
--- a/page-download.c Fri Mar 17 10:49:24 2023 +0100 +++ b/page-download.c Fri Mar 17 20:01:00 2023 +0100 @@ -28,7 +28,7 @@ { struct paste paste; - if (database_get(&paste, req->path) < 0) + if (database_get(&database, &paste, req->path) < 0) page_status(req, KHTTP_404); else { khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_OCTET_STREAM]);
--- a/page-fork.c Fri Mar 17 10:49:24 2023 +0100 +++ b/page-fork.c Fri Mar 17 20:01:00 2023 +0100 @@ -29,7 +29,7 @@ { struct paste paste; - if (database_get(&paste, req->path) < 0) + if (database_get(&database, &paste, req->path) < 0) page_status(req, KHTTP_404); else { page_new_render(req, &paste);
--- a/page-index.c Fri Mar 17 10:49:24 2023 +0100 +++ b/page-index.c Fri Mar 17 20:01:00 2023 +0100 @@ -106,7 +106,7 @@ struct paste pastes[LIMIT]; size_t pastesz = NELEM(pastes); - if (database_recents(pastes, &pastesz) < 0) + if (database_recents(&database, pastes, &pastesz) < 0) page_status(req, KHTTP_500); else { page_index_render(req, pastes, pastesz);
--- a/page-new.c Fri Mar 17 10:49:24 2023 +0100 +++ b/page-new.c Fri Mar 17 20:01:00 2023 +0100 @@ -150,7 +150,7 @@ raw = strcmp(val, "on") == 0; } - if (database_insert(&paste) < 0) + if (database_insert(&database, &paste) < 0) page_status(req, KHTTP_500); else { scheme = req->scheme == KSCHEME_HTTP ? "http" : "https";
--- a/page-paste.c Fri Mar 17 10:49:24 2023 +0100 +++ b/page-paste.c Fri Mar 17 20:01:00 2023 +0100 @@ -111,7 +111,7 @@ } }; - if (database_get(&self.paste, req->path) < 0) + if (database_get(&database, &self.paste, req->path) < 0) page_status(req, KHTTP_404); else { page(req, KHTTP_200, TITLE, HTML, &self.template);
--- a/page-search.c Fri Mar 17 10:49:24 2023 +0100 +++ b/page-search.c Fri Mar 17 20:01:00 2023 +0100 @@ -110,7 +110,7 @@ if (author && strlen(author) == 0) author = NULL; - if (database_search(pastes, &pastesz, title, author, language) < 0) + if (database_search(&database, pastes, &pastesz, title, author, language) < 0) page_status(req, KHTTP_500); else { page_index_render(req, pastes, pastesz);
--- a/pasterd.c Fri Mar 17 10:49:24 2023 +0100 +++ b/pasterd.c Fri Mar 17 20:01:00 2023 +0100 @@ -16,8 +16,11 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <errno.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <time.h> #include <unistd.h> @@ -27,6 +30,31 @@ #include "log.h" #include "util.h" +/* + * Interval in seconds for each cleanup time. Since a paste has one hour + * duration as minimal let's cleanup every hour. + */ +#define CLEANUP_INTERVAL 3600 + +static timer_t timer; + +/* + * This function runs in a thread, we open our own local database to let the + * engine locks by itself. + */ +static void +cleanup(union sigval val) +{ + (void)val; + + struct database db; + + if (database_open(&db, config.databasepath) == 0) { + database_clear(&db); + database_finish(&db); + } +} + static void defaults(void) { @@ -37,13 +65,26 @@ static void init(void) { + struct sigevent sigev = { + .sigev_notify = SIGEV_THREAD, + .sigev_notify_function = cleanup + }; + struct itimerspec spec = { + .it_value = { .tv_sec = CLEANUP_INTERVAL }, + .it_interval = { .tv_sec = CLEANUP_INTERVAL } + }; + srand(time(NULL)); log_open(); if (!config.databasepath[0]) die("abort: no database specified\n"); - if (database_open(config.databasepath) < 0) + if (database_open(&database, config.databasepath) < 0) die("abort: could not open database\n"); + if (timer_create(CLOCK_MONOTONIC, &sigev, &timer) < 0) + die("abort: timer_create: %s\n", strerror(errno)); + if (timer_settime(timer, 0, &spec, NULL) < 0) + die("abort: timer_settime: %s\n", strerror(errno)); } static void @@ -55,14 +96,14 @@ static void finish(void) { - database_finish(); + database_finish(&database); log_finish(); } static void usage(void) { - fprintf(stderr, "usage: paster [-fqv] [-d database-path] [-t theme-directory]\n"); + fprintf(stderr, "usage: paster [-qv] [-d database-path] [-t theme-directory]\n"); exit(1); }