]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
liblastlog2: add support for WAL journal mode
authorWanBingjiang <wanbingjiang@webray.com.cn>
Wed, 22 Apr 2026 05:46:56 +0000 (13:46 +0800)
committerWanBingjiang <wanbingjiang@webray.com.cn>
Fri, 24 Apr 2026 08:50:07 +0000 (16:50 +0800)
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 <wanbingjiang@webray.com.cn>
liblastlog2/man/ll2_get_journal_mode.3.adoc [new file with mode: 0644]
liblastlog2/man/ll2_set_journal_mode.3.adoc [new file with mode: 0644]
liblastlog2/src/lastlog2.c
liblastlog2/src/lastlog2.h.in
liblastlog2/src/liblastlog2.sym

diff --git a/liblastlog2/man/ll2_get_journal_mode.3.adoc b/liblastlog2/man/ll2_get_journal_mode.3.adoc
new file mode 100644 (file)
index 0000000..f9f0df3
--- /dev/null
@@ -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 <lastlog2.h>*
+
+*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 (file)
index 0000000..cd1046d
--- /dev/null
@@ -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 <lastlog2.h>*
+
+*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::[]
index def17e31197fd39d2227cdffa01724998eb88705..4e9f98b06ba4c2fa144b85cd371d7b129ef86dbd 100644 (file)
@@ -26,6 +26,7 @@
 */
 
 #include <pwd.h>
+#include <ctype.h>
 #include <errno.h>
 #include <time.h>
 #include <stdio.h>
@@ -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;
+}
index bc8ce23ee9fb9fd0526a8ac624a4254536c11c0e..c7ab3a56a861518d604664eef47a3fd6f7df0554 100644 (file)
@@ -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
index c21cced1ad0dd37754a2e18fca117fe0e4a4503f..461b224d768750707a3c3c1699ab59b52a83cb22 100644 (file)
@@ -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;