From aeaf40798f335541c380f2d4e8aa5e9141e50e52 Mon Sep 17 00:00:00 2001 From: Jens Elkner Date: Tue, 4 Nov 2025 01:02:19 +0100 Subject: [PATCH] fix #47: better lastlog - use option -u to show the most recent log entry for each user, only. - option letter u: inspired by 'sort -u' (unique) --- include/wtmpdb.h | 4 ++-- lib/libwtmpdb.c | 12 ++++++------ lib/sqlite.c | 8 ++++++-- lib/sqlite.h | 2 +- lib/varlink.c | 15 +++++++++++++-- lib/varlink.h | 2 +- man/wtmpdb.8.xml | 13 +++++++++++++ src/varlink-org.openSUSE.wtmpdb.c | 1 + src/wtmpdb.c | 16 +++++++++++++--- src/wtmpdbd.c | 10 ++++++++-- tests/tst-login-logout.c | 4 ++-- 11 files changed, 66 insertions(+), 21 deletions(-) diff --git a/include/wtmpdb.h b/include/wtmpdb.h index 416fde8..afc9567 100644 --- a/include/wtmpdb.h +++ b/include/wtmpdb.h @@ -57,11 +57,11 @@ extern int64_t wtmpdb_login (const char *db_path, int type, const char *service, char **error); extern int wtmpdb_logout (const char *db_path, int64_t id, uint64_t usec_logout, char **error); -extern int wtmpdb_read_all (const char *db_path, +extern int wtmpdb_read_all (const char *db_path, int uniq, int (*cb_func) (void *unused, int argc, char **argv, char **azColName), char **error); -extern int wtmpdb_read_all_v2 (const char *db_path, +extern int wtmpdb_read_all_v2 (const char *db_path, int uniq, int (*cb_func) (void *unused, int argc, char **argv, char **azColName), void *userdata, char **error); diff --git a/lib/libwtmpdb.c b/lib/libwtmpdb.c index 8e5f2d9..a9826f8 100644 --- a/lib/libwtmpdb.c +++ b/lib/libwtmpdb.c @@ -158,7 +158,7 @@ wtmpdb_get_id (const char *db_path, const char *tty, char **error) each entry. Returns 0 on success, -1 on failure. */ int -wtmpdb_read_all (const char *db_path, +wtmpdb_read_all (const char *db_path, int uniq, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), char **error) @@ -168,7 +168,7 @@ wtmpdb_read_all (const char *db_path, #if WITH_WTMPDBD int r; - r = varlink_read_all (cb_func, NULL, error); + r = varlink_read_all (uniq, cb_func, NULL, error); if (r >= 0) return r; @@ -185,11 +185,11 @@ wtmpdb_read_all (const char *db_path, #endif } - return sqlite_read_all (db_path?db_path:_PATH_WTMPDB, cb_func, NULL, error); + return sqlite_read_all (db_path?db_path:_PATH_WTMPDB, uniq, cb_func, NULL, error); } int -wtmpdb_read_all_v2 (const char *db_path, +wtmpdb_read_all_v2 (const char *db_path, int uniq, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), void *userdata, char **error) @@ -199,7 +199,7 @@ wtmpdb_read_all_v2 (const char *db_path, #if WITH_WTMPDBD int r; - r = varlink_read_all (cb_func, userdata, error); + r = varlink_read_all (uniq, cb_func, userdata, error); if (r >= 0) return r; @@ -216,7 +216,7 @@ wtmpdb_read_all_v2 (const char *db_path, #endif } - return sqlite_read_all (db_path?db_path:_PATH_WTMPDB, cb_func, userdata, error); + return sqlite_read_all (db_path?db_path:_PATH_WTMPDB, uniq, cb_func, userdata, error); } diff --git a/lib/sqlite.c b/lib/sqlite.c index 0f3090c..5efeda5 100644 --- a/lib/sqlite.c +++ b/lib/sqlite.c @@ -458,7 +458,7 @@ sqlite_get_id (const char *db_path, const char *tty, char **error) each entry. Returns 0 on success, -1 on failure. */ int -sqlite_read_all (const char *db_path, +sqlite_read_all (const char *db_path, int uniq, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), void *userdata, char **error) @@ -471,7 +471,11 @@ sqlite_read_all (const char *db_path, if (r != 0) return -r; - char *sql = "SELECT * FROM wtmp ORDER BY Login DESC, Logout ASC"; + char *sql = uniq + ? "SELECT ID, Type, User, Login, Logout, TTY, RemoteHost, Service FROM (" + "SELECT *,ROW_NUMBER() OVER (PARTITION BY User ORDER BY Login DESC) AS rn " + "FROM wtmp WHERE Login IS NOT NULL AND TTY != '~') WHERE rn = 1" + : "SELECT * FROM wtmp ORDER BY Login DESC, Logout ASC"; r = sqlite3_exec (db, sql, cb_func, userdata, &err_msg); sqlite3_close (db); diff --git a/lib/sqlite.h b/lib/sqlite.h index c398aa7..d0bb2a8 100644 --- a/lib/sqlite.h +++ b/lib/sqlite.h @@ -37,7 +37,7 @@ extern int sqlite_logout (const char *db_path, int64_t id, uint64_t usec_logout, char **error); extern int64_t sqlite_get_id (const char *db_path, const char *tty, char **error); -extern int sqlite_read_all (const char *db_path, +extern int sqlite_read_all (const char *db_path, int uniq, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), void *userdata, char **error); diff --git a/lib/varlink.c b/lib/varlink.c index 3ba10ac..cc18c06 100644 --- a/lib/varlink.c +++ b/lib/varlink.c @@ -523,7 +523,7 @@ wtmpdb_entry_free (struct wtmpdb_entry *var) } int -varlink_read_all (int (*cb_func)(void *unused, int argc, char **argv, +varlink_read_all (int uniq, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), void *userdata, char **error) { @@ -539,6 +539,7 @@ varlink_read_all (int (*cb_func)(void *unused, int argc, char **argv, {} }; _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; sd_json_variant *result; int r; @@ -546,8 +547,18 @@ varlink_read_all (int (*cb_func)(void *unused, int argc, char **argv, if (r < 0) return r; + r = sd_json_buildo(¶ms, SD_JSON_BUILD_PAIR("Uniq", SD_JSON_BUILD_INTEGER(uniq))); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to build JSON data: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + const char *error_id; - r = sd_varlink_call(link, "org.openSUSE.wtmpdb.ReadAll", NULL, &result, &error_id); + r = sd_varlink_call(link, "org.openSUSE.wtmpdb.ReadAll", params, &result, &error_id); if (r < 0) { if (error) diff --git a/lib/varlink.h b/lib/varlink.h index 79dd444..2ebbdc4 100644 --- a/lib/varlink.h +++ b/lib/varlink.h @@ -35,7 +35,7 @@ extern int64_t varlink_login (int type, const char *user, char **error); extern int varlink_logout (int64_t id, uint64_t usec_logout, char **error); extern int64_t varlink_get_id (const char *tty, char **error); -extern int varlink_read_all (int (*cb_func)(void *unused, int argc, char **argv, +extern int varlink_read_all (int uniq, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), void *userdata, char **error); extern int varlink_get_boottime (uint64_t *boottime, char **error); diff --git a/man/wtmpdb.8.xml b/man/wtmpdb.8.xml index 1cd07ca..086dc8b 100644 --- a/man/wtmpdb.8.xml +++ b/man/wtmpdb.8.xml @@ -175,6 +175,19 @@ + + + + + + + Display only the most recent log entry for each user. + This is the default when called as lastlog. + In contrast to the legacy lastlog tool, users without any log entries + will not be shown. + + + diff --git a/src/varlink-org.openSUSE.wtmpdb.c b/src/varlink-org.openSUSE.wtmpdb.c index ea2dc63..3e62220 100644 --- a/src/varlink-org.openSUSE.wtmpdb.c +++ b/src/varlink-org.openSUSE.wtmpdb.c @@ -49,6 +49,7 @@ static SD_VARLINK_DEFINE_METHOD( static SD_VARLINK_DEFINE_METHOD( ReadAll, SD_VARLINK_FIELD_COMMENT("Get all entries from the database"), + SD_VARLINK_DEFINE_INPUT(Uniq, SD_VARLINK_INT, 0), SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Data, WtmpdbEntry, SD_VARLINK_ARRAY | SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); diff --git a/src/wtmpdb.c b/src/wtmpdb.c index 4bd4671..8af15c8 100644 --- a/src/wtmpdb.c +++ b/src/wtmpdb.c @@ -80,6 +80,7 @@ static int iflag = 0; static int jflag = 0; static int wflag = 0; static int xflag = 0; +static int uniq = 0; static const int name_len = 8; /* LAST_LOGIN_LEN */ static int login_fmt = TIMEFMT_SHORT; static int login_len = 16; /* 16 = short, 24 = full */ @@ -663,6 +664,7 @@ usage (int retval) fputs (" -S, --service Display PAM service used to login\n", output); fputs (" -s, --since TIME Display who was logged in after TIME\n", output); fputs (" -t, --until TIME Display who was logged in until TIME\n", output); + fputs (" -u, --unique Display the latest entry for each user, only.\n", output); fputs (" -w, --fullnames Display full IP addresses and user and domain names\n", output); fputs (" -x, --system Display system shutdown entries\n", output); fputs (" --time-format FORMAT Display timestamps in the specified FORMAT:\n", output); @@ -780,6 +782,7 @@ main_last (int argc, char **argv) {"service", no_argument, NULL, 'S'}, {"since", required_argument, NULL, 's'}, {"system", no_argument, NULL, 'x'}, + {"unique", no_argument, NULL, 'u'}, {"until", required_argument, NULL, 't'}, {"time-format", required_argument, NULL, TIMEFMT_VALUE}, {"json", no_argument, NULL, 'j'}, @@ -789,7 +792,7 @@ main_last (int argc, char **argv) char *error = NULL; int c; - while ((c = getopt_long (argc, argv, "0123456789adf:Fijn:p:RSs:t:wx", + while ((c = getopt_long (argc, argv, "0123456789adf:Fijn:p:RSs:t:uwx", longopts, NULL)) != -1) { switch (c) @@ -857,6 +860,9 @@ main_last (int argc, char **argv) exit (EXIT_FAILURE); } break; + case 'u': + uniq = 1; + break; case 'w': wflag = 1; break; @@ -907,7 +913,7 @@ main_last (int argc, char **argv) if (jflag) printf ("{\n \"entries\": [\n"); - if (wtmpdb_read_all (wtmpdb_path, print_entry, &error) != 0) + if (wtmpdb_read_all (wtmpdb_path, uniq, print_entry, &error) != 0) { if (error) { @@ -1282,7 +1288,11 @@ main (int argc, char **argv) if (strcmp (basename(argv[0]), "last") == 0) return main_last (argc, argv); - else if (argc == 1) + if (strcmp (basename(argv[0]), "lastlog") == 0) { + uniq = 1; + return main_last (argc, argv); + } + if (argc == 1) usage (EXIT_SUCCESS); else if (strcmp (argv[1], "last") == 0) return main_last (--argc, ++argv); diff --git a/src/wtmpdbd.c b/src/wtmpdbd.c index 7f7528a..3f09b53 100644 --- a/src/wtmpdbd.c +++ b/src/wtmpdbd.c @@ -473,8 +473,14 @@ vl_method_read_all(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { + struct p { + int uniq; + } p { + .uniq = 0 + }; _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; static const sd_json_dispatch_field dispatch_table[] = { + { "Uniq", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct p, uniq), SD_JSON_MANDATORY }, {} }; _cleanup_(freep) char *error = NULL; @@ -482,7 +488,7 @@ vl_method_read_all(sd_varlink *link, sd_json_variant *parameters, log_msg (LOG_INFO, "Varlink method \"ReadAll\" called..."); - r = sd_varlink_dispatch(link, parameters, dispatch_table, /* userdata= */ NULL); + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); if (r != 0) { log_msg(LOG_ERR, "Get all entries request: varlink dispatch failed: %s", strerror (-r)); @@ -490,7 +496,7 @@ vl_method_read_all(sd_varlink *link, sd_json_variant *parameters, } incomplete = 0; - r = wtmpdb_read_all_v2 (_PATH_WTMPDB, &wtmpdb_cb_func, (void *)&array, &error); + r = wtmpdb_read_all_v2 (_PATH_WTMPDB, p.uniq, &wtmpdb_cb_func, (void *)&array, &error); if (r < 0 || error != NULL || incomplete) { log_msg(LOG_ERR, "Didn't got all entries from db: %s", error); diff --git a/tests/tst-login-logout.c b/tests/tst-login-logout.c index fd8b21d..1c4d164 100644 --- a/tests/tst-login-logout.c +++ b/tests/tst-login-logout.c @@ -103,7 +103,7 @@ test_rotate (const char *db_path, const int days) char *error = NULL; counter = 0; - if (wtmpdb_read_all (db_path, count_entry, &error) != 0) + if (wtmpdb_read_all (db_path, 0, count_entry, &error) != 0) { if (error) { @@ -134,7 +134,7 @@ test_rotate (const char *db_path, const int days) } counter = 0; - if (wtmpdb_read_all (db_path, count_entry, &error) != 0) + if (wtmpdb_read_all (db_path, 0, count_entry, &error) != 0) { if (error) {