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);
 }