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)
{