From: WanBingjiang Date: Wed, 22 Apr 2026 05:46:56 +0000 (+0800) Subject: liblastlog2: add support for WAL journal mode X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=b9b569a67bd9a99eb9f70eb91c5e74708a984c47;p=thirdparty%2Futil-linux.git liblastlog2: add support for WAL journal mode Add ll2_set_journal_mode() and ll2_get_journal_mode() functions to allow setting and querying SQLite journal modes. This enables users to switch to WAL (Write-Ahead Logging) mode for better concurrency in high-traffic scenarios. WAL mode allows readers and writers to operate concurrently without blocking each other, significantly reducing database lock contention in environments with frequent SSH logins. The journal mode setting is persistent and only needs to be set once per database. Assisted-by: Claude:claude-sonnet-4.5 Signed-off-by: WanBingjiang --- diff --git a/liblastlog2/man/ll2_get_journal_mode.3.adoc b/liblastlog2/man/ll2_get_journal_mode.3.adoc new file mode 100644 index 000000000..f9f0df3b4 --- /dev/null +++ b/liblastlog2/man/ll2_get_journal_mode.3.adoc @@ -0,0 +1,96 @@ +//po4a: entry man manual += ll2_get_journal_mode(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.43 + +== NAME + +ll2_get_journal_mode - get current SQLite journal mode for lastlog2 database + +== SYNOPSIS + +*#include * + +*int ll2_get_journal_mode(struct ll2_context *__context__, char **__mode__, char **__error__);* + +== DESCRIPTION + +The *ll2_get_journal_mode*() function retrieves the current SQLite journal mode +for the lastlog2 database associated with _context_. + +If _context_ is NULL, the default database, defined in _LL2_DEFAULT_DATABASE_, +will be taken. + +The returned journal mode string is in uppercase (e.g., "WAL", "DELETE") for +consistency with user input. + +Common journal modes include: + +* *WAL* - Write-Ahead Logging mode +* *DELETE* - Default mode +* *TRUNCATE* - Truncate mode +* *PERSIST* - Persist mode +* *MEMORY* - Memory mode +* *OFF* - Journaling disabled + +-------------------------------------- +struct ll2_context *ctx; +char *mode = NULL; +char *error = NULL; + +ctx = ll2_new_context(NULL); +if (!ctx) { + fprintf(stderr, "Failed to create context\n"); + return 1; +} + +if (ll2_get_journal_mode(ctx, &mode, &error) != 0) { + fprintf(stderr, "Failed to get journal mode: %s\n", error); + free(error); + ll2_unref_context(ctx); + return 1; +} + +printf("Current journal mode: %s\n", mode); +free(mode); +ll2_unref_context(ctx); +-------------------------------------- + +== RETURN VALUE + +Returns 0 on success, -ENOMEM or -1 on other failure. +_error_ contains an error string if the return value is -1. +_error_ is not guaranteed to contain an error string, could also be NULL. +_error_ should be freed by the caller. +If successful, _mode_ will be set to a newly allocated string containing +the current journal mode. The caller must free this string. + +== AUTHORS + +mailto:kukuk@suse.de[Thorsten Kukuk], +mailto:wanbingjiang@webray.com.cn[WanBingjiang] + +== SEE ALSO + +*lastlog2*(3), +*ll2_set_journal_mode*(3), +*ll2_new_context*(3), +*ll2_unref_context*(3), +*ll2_read_all*(3), +*ll2_write_entry*(3), +*ll2_read_entry*(3), +*ll2_remove_entry*(3), +*ll2_update_login_time*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_set_journal_mode.3.adoc b/liblastlog2/man/ll2_set_journal_mode.3.adoc new file mode 100644 index 000000000..cd1046d9d --- /dev/null +++ b/liblastlog2/man/ll2_set_journal_mode.3.adoc @@ -0,0 +1,96 @@ +//po4a: entry man manual += ll2_set_journal_mode(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.43 + +== NAME + +ll2_set_journal_mode - set SQLite journal mode for lastlog2 database + +== SYNOPSIS + +*#include * + +*int ll2_set_journal_mode(struct ll2_context *__context__, const char *__mode__, char **__error__);* + +== DESCRIPTION + +The *ll2_set_journal_mode*() function sets the SQLite journal mode for the +lastlog2 database associated with _context_ to the specified _mode_. + +If _context_ is NULL, the default database, defined in _LL2_DEFAULT_DATABASE_, +will be taken. + +The journal mode setting is persistent and will be retained across database +connections. + +Common journal modes include: + +* *WAL* - Write-Ahead Logging mode (recommended for high concurrency) +* *DELETE* - Default mode, deletes journal file after commit +* *TRUNCATE* - Truncates journal file to zero length after commit +* *PERSIST* - Keeps journal file after commit +* *MEMORY* - Stores journal in memory +* *OFF* - Disables journaling (not recommended) + +WAL (Write-Ahead Logging) mode is recommended for high-concurrency scenarios +as it allows readers and writers to operate concurrently without blocking +each other. This significantly reduces database lock contention in environments +with frequent SSH logins. + +-------------------------------------- +struct ll2_context *ctx; +char *error = NULL; + +ctx = ll2_new_context(NULL); +if (!ctx) { + fprintf(stderr, "Failed to create context\n"); + return 1; +} + +if (ll2_set_journal_mode(ctx, "WAL", &error) != 0) { + fprintf(stderr, "Failed to set journal mode: %s\n", error); + free(error); + ll2_unref_context(ctx); + return 1; +} + +ll2_unref_context(ctx); +-------------------------------------- + +== RETURN VALUE + +Returns 0 on success, -ENOMEM or -1 on other failure. +_error_ contains an error string if the return value is -1. +_error_ is not guaranteed to contain an error string, could also be NULL. +_error_ should be freed by the caller. + +== AUTHORS + +mailto:kukuk@suse.de[Thorsten Kukuk], +mailto:wanbingjiang@webray.com.cn[WanBingjiang] + +== SEE ALSO + +*lastlog2*(3), +*ll2_get_journal_mode*(3), +*ll2_new_context*(3), +*ll2_unref_context*(3), +*ll2_read_all*(3), +*ll2_write_entry*(3), +*ll2_read_entry*(3), +*ll2_remove_entry*(3), +*ll2_update_login_time*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/src/lastlog2.c b/liblastlog2/src/lastlog2.c index def17e311..4e9f98b06 100644 --- a/liblastlog2/src/lastlog2.c +++ b/liblastlog2/src/lastlog2.c @@ -26,6 +26,7 @@ */ #include +#include #include #include #include @@ -660,3 +661,128 @@ done: return retval; } + +static const char *valid_journal_modes[] = { + "WAL", "DELETE", "TRUNCATE", "PERSIST", "MEMORY", "OFF", NULL +}; + +static int +is_valid_journal_mode(const char *mode) +{ + for (size_t i = 0; valid_journal_modes[i]; i++) + if (strcasecmp(mode, valid_journal_modes[i]) == 0) + return 1; + return 0; +} + +/* Set journal mode. + Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int +ll2_set_journal_mode(struct ll2_context *context, const char *mode, + char **error) +{ + sqlite3 *db; + sqlite3_stmt *res = NULL; + const unsigned char *actual; + char *sql = NULL; + int retval = 0; + + if (!mode || !is_valid_journal_mode(mode)) { + if (error && asprintf(error, "Invalid journal mode: %s", mode ? mode : "(null)") < 0) + return -ENOMEM; + return -1; + } + + retval = open_database_rw(context, &db, error); + if (retval != 0) + return retval; + + if (asprintf(&sql, "PRAGMA journal_mode = %s;", mode) < 0) { + sqlite3_close(db); + return -ENOMEM; + } + + if (sqlite3_prepare_v2(db, sql, -1, &res, 0) != SQLITE_OK) { + retval = -1; + if (error && asprintf(error, "Failed to set journal mode to %s: %s", + mode, sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out; + } + + if (sqlite3_step(res) != SQLITE_ROW) { + retval = -1; + if (error && asprintf(error, "Failed to set journal mode to %s: %s", + mode, sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out; + } + + /* Verify SQLite actually applied the requested mode */ + actual = sqlite3_column_text(res, 0); + if (!actual || strcasecmp((const char *)actual, mode) != 0) { + retval = -1; + if (error && asprintf(error, + "Journal mode not changed: requested %s, got %s", + mode, actual ? (const char *)actual : "(null)") < 0) + retval = -ENOMEM; + } + +out: + if (res) + sqlite3_finalize(res); + free(sql); + sqlite3_close(db); + + return retval; +} + +/* Get current journal mode. + Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int +ll2_get_journal_mode(struct ll2_context *context, char **mode, char **error) +{ + sqlite3 *db; + sqlite3_stmt *res = NULL; + int retval = 0; + static const char *sql = "PRAGMA journal_mode;"; + + retval = open_database_ro(context, &db, error); + if (retval != 0) + return retval; + + if (sqlite3_prepare_v2(db, sql, -1, &res, 0) != SQLITE_OK) { + retval = -1; + if (error && asprintf(error, "Failed to query journal mode: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out; + } + + int step = sqlite3_step(res); + if (step == SQLITE_ROW) { + const unsigned char *mode_str = sqlite3_column_text(res, 0); + if (mode_str && mode) { + *mode = strdup((const char *)mode_str); + if (*mode == NULL) { + retval = -ENOMEM; + } else { + /* Convert to uppercase for consistency */ + for (char *p = *mode; *p; p++) + *p = toupper((unsigned char)*p); + } + } + } else { + retval = -1; + if (error && asprintf(error, "Failed to get journal mode: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + } + +out: + if (res) + sqlite3_finalize(res); + sqlite3_close(db); + + return retval; +} diff --git a/liblastlog2/src/lastlog2.h.in b/liblastlog2/src/lastlog2.h.in index bc8ce23ee..c7ab3a56a 100644 --- a/liblastlog2/src/lastlog2.h.in +++ b/liblastlog2/src/lastlog2.h.in @@ -89,6 +89,17 @@ extern int ll2_rename_user (struct ll2_context *context, const char *user, extern int ll2_import_lastlog (struct ll2_context *context, const char *lastlog_file, char **error); +/* Set journal mode (e.g., "WAL", "DELETE"). + Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int ll2_set_journal_mode (struct ll2_context *context, + const char *mode, char **error); + +/* Get current journal mode. + Returns 0 on success, -ENOMEM or -1 on other failure. + Caller must free the returned mode string. */ +extern int ll2_get_journal_mode (struct ll2_context *context, + char **mode, char **error); + #ifdef __cplusplus } #endif diff --git a/liblastlog2/src/liblastlog2.sym b/liblastlog2/src/liblastlog2.sym index c21cced1a..461b224d7 100644 --- a/liblastlog2/src/liblastlog2.sym +++ b/liblastlog2/src/liblastlog2.sym @@ -11,3 +11,9 @@ LIBLASTLOG2_2_40 { ll2_import_lastlog; local: *; }; + +LIBLASTLOG2_2_43 { + global: + ll2_set_journal_mode; + ll2_get_journal_mode; +} LIBLASTLOG2_2_40;