From cb823cc8f9dfb648d6d64bb3528ffb7e7775c8d8 Mon Sep 17 00:00:00 2001 From: Jens Elkner Date: Thu, 30 Oct 2025 19:33:37 +0100 Subject: [PATCH 1/6] wtmpdb: show sub command relevant options, only - pass the cmd ID to usage() so that it knows, what to show - let all sub commands accept -v and -h - if no sub command is given, ignore the -f ... option (not needed) --- src/wtmpdb.c | 223 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 151 insertions(+), 72 deletions(-) diff --git a/src/wtmpdb.c b/src/wtmpdb.c index 4bd4671..64e1589 100644 --- a/src/wtmpdb.c +++ b/src/wtmpdb.c @@ -93,6 +93,22 @@ static time_t since = 0; /* Who was logged in after this time? */ static time_t until = 0; /* Who was logged in until this time? */ static char **match = NULL; /* user/tty to display only */ +typedef enum cmd_idx { + CMD_NONE = 0, + CMD_LAST, + CMD_BOOT, + CMD_SHUTDOWN, + CMD_BOOTTIME, + CMD_ROTATE, + CMD_IMPORT, + CMD_MAX /* per contract the always the last one */ +} cmd_idx_t; + +static const char *cmd_name[] = { + "unknown", "last", "boot", "shutdown", "boottime", + "rotate", "import", NULL +}; + /* isipaddr - find out if string provided is an IP address or not 0 - no IP address @@ -644,16 +660,37 @@ print_entry (void *unused __attribute__((__unused__)), } static void -usage (int retval) +show_version(void) { - FILE *output = (retval != EXIT_SUCCESS) ? stderr : stdout; + printf ("wtmpdb %s\n", VERSION); + exit(EXIT_SUCCESS); +} - fprintf (output, "Usage: wtmpdb [command] [options]\n"); - fputs ("Commands: last, boot, boottime, rotate, shutdown, import\n\n", output); - fputs ("Options for last:\n", output); +static void +usage (int retval, cmd_idx_t cmd) +{ + FILE *output = (retval != EXIT_SUCCESS) ? stderr : stdout; + int i; + + if (cmd == CMD_NONE) { + fprintf (output, "Usage: wtmpdb [command] [options] [operand]\n"); + fprintf (output, "\nCommands: %s", cmd_name[1]); + for (i=2; i < CMD_MAX; i++) + fprintf(output, ", %s", cmd_name[i]); + fputs("\n\nCommon options:\n", output); + } else { + fprintf (output, "Usage: wtmpdb %s [options]%s\n", cmd_name[cmd], + (cmd == CMD_LAST || cmd == CMD_IMPORT) ? " [operand]" : ""); + fputs ("\nOptions:\n", output); + } + fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); + fputs (" -h, --help Display this help message and exit\n", output); + fputs (" -v, --version Print version number and exit\n", output); + if (cmd == CMD_NONE) + fprintf (output, "\nOptions for %s:\n", cmd_name[CMD_LAST]); + if (cmd == CMD_LAST || cmd == CMD_NONE) { fputs (" -a, --hostlast Display hostnames as last entry\n", output); fputs (" -d, --dns Translate IP addresses into a hostname\n", output); - fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); fputs (" -F, --fulltimes Display full times and dates\n", output); fputs (" -i, --ip Translate hostnames to IP addresses\n", output); fputs (" -j, --json Generate JSON output\n", output); @@ -665,40 +702,30 @@ usage (int retval) fputs (" -t, --until TIME Display who was logged in until TIME\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); - fputs (" notime|short|full|iso\n", output); - - fputs (" [username...] Display only entries matching these arguments\n", output); - fputs (" [tty...] Display only entries matching these arguments\n", output); - fputs ("TIME must be in the format \"YYYY-MM-DD HH:MM:SS\"\n", output); - fputs ("\n", output); - - fputs ("Options for boot (writes boot entry to wtmpdb):\n", output); - fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); - fputs ("\n", output); - - fputs ("Options for boottime (print time of last system boot):\n", output); - fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); - fputs ("\n", output); - - fputs ("Options for rotate (exports old entries to wtmpdb_)):\n", output); - fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); + fputs (" --time-format FMT Display timestamps in the specified format.\n", output); + fputs ("\n FMT format: notime|short|full|iso\n", output); + fputs (" TIME format: YYYY-MM-DD HH:MM:SS\n", output); + } + if (cmd == CMD_NONE) + fprintf (output, "\nOperands for %s:\n", cmd_name[CMD_LAST]); + if (cmd == CMD_LAST) { + fputs ("\nOperands:\n", output); + } + if (cmd == CMD_LAST || cmd == CMD_NONE) { + fputs (" username... Display only entries matching these arguments\n", output); + fputs (" tty... Display only entries matching these arguments\n", output); + } + if (cmd == CMD_NONE) + fputs ("\nOptions for rotate (exports old entries to wtmpdb_)):\n", output); + if (cmd == CMD_NONE || cmd == CMD_ROTATE) { fputs (" -d, --days INTEGER Export all entries which are older than the given days\n", output); - fputs ("\n", output); - - fputs ("Options for shutdown (writes shutdown time to wtmpdb):\n", output); - fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); - fputs ("\n", output); - - fputs ("Options for import (imports legacy wtmp logs):\n", output); - fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); + } + if (cmd == CMD_NONE) + fprintf (output, "\nOperands for %s:\n", cmd_name[CMD_IMPORT]); + if (cmd == CMD_IMPORT) + fputs ("\nOperands:\n", output); + if (cmd == CMD_IMPORT || cmd == CMD_NONE) fputs (" logs... Legacy log files to import\n", output); - fputs ("\n", output); - - fputs ("Generic options:\n", output); - fputs (" -h, --help Display this help message and exit\n", output); - fputs (" -v, --version Print version number and exit\n", output); - fputs ("\n", output); exit (retval); } @@ -706,6 +733,8 @@ static int main_rotate (int argc, char **argv) { struct option const longopts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, {"file", required_argument, NULL, 'f'}, {"days", no_argument, NULL, 'd'}, {NULL, 0, NULL, '\0'} @@ -717,7 +746,7 @@ main_rotate (int argc, char **argv) int c; - while ((c = getopt_long (argc, argv, "f:d:", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "f:d:hv", longopts, NULL)) != -1) { switch (c) { @@ -727,8 +756,14 @@ main_rotate (int argc, char **argv) case 'd': days = atoi (optarg); break; + case 'v': + show_version(); + break; + case 'h': + usage (EXIT_SUCCESS, CMD_ROTATE); + break; default: - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_ROTATE); break; } } @@ -736,7 +771,7 @@ main_rotate (int argc, char **argv) if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_ROTATE); } if (wtmpdb_rotate (wtmpdb_path, days, &error, @@ -768,6 +803,8 @@ static int main_last (int argc, char **argv) { struct option const longopts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, {"hostlast", no_argument, NULL, 'a'}, {"dns", no_argument, NULL, 'd'}, {"file", required_argument, NULL, 'f'}, @@ -789,7 +826,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:Fhijn:p:RSs:t:vwx", longopts, NULL)) != -1) { switch (c) @@ -871,8 +908,14 @@ main_last (int argc, char **argv) exit (EXIT_FAILURE); } break; + case 'v': + show_version(); + break; + case 'h': + usage (EXIT_SUCCESS, CMD_LAST); + break; default: - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_LAST); break; } } @@ -883,25 +926,25 @@ main_last (int argc, char **argv) if (nohostname && hostlast) { fprintf (stderr, "The options -a and -R cannot be used together.\n"); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_LAST); } if (nohostname && dflag) { fprintf (stderr, "The options -d and -R cannot be used together.\n"); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_LAST); } if (nohostname && iflag) { fprintf (stderr, "The options -i and -R cannot be used together.\n"); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_LAST); } if (dflag && iflag) { fprintf (stderr, "The options -d and -i cannot be used together.\n"); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_LAST); } if (jflag) @@ -1024,6 +1067,8 @@ static int main_boot (int argc, char **argv) { struct option const longopts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, {"file", required_argument, NULL, 'f'}, {"quiet", no_argument, NULL, 'q'}, {NULL, 0, NULL, '\0'} @@ -1035,7 +1080,7 @@ main_boot (int argc, char **argv) int quiet = 0; #endif - while ((c = getopt_long (argc, argv, "f:q", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "f:hqv", longopts, NULL)) != -1) { switch (c) { @@ -1047,8 +1092,14 @@ main_boot (int argc, char **argv) quiet = 1; #endif break; + case 'v': + show_version(); + break; + case 'h': + usage (EXIT_SUCCESS, CMD_BOOT); + break; default: - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_BOOT); break; } } @@ -1056,7 +1107,7 @@ main_boot (int argc, char **argv) if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_BOOT); } struct utsname uts; @@ -1125,6 +1176,8 @@ static int main_boottime (int argc, char **argv) { struct option const longopts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, {"file", required_argument, NULL, 'f'}, {NULL, 0, NULL, '\0'} }; @@ -1132,15 +1185,21 @@ main_boottime (int argc, char **argv) int c; uint64_t boottime; - while ((c = getopt_long (argc, argv, "f:", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "f:hv", longopts, NULL)) != -1) { switch (c) { case 'f': wtmpdb_path = optarg; break; + case 'v': + show_version(); + break; + case 'h': + usage (EXIT_SUCCESS, CMD_BOOTTIME); + break; default: - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_BOOTTIME); break; } } @@ -1148,7 +1207,7 @@ main_boottime (int argc, char **argv) if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_BOOTTIME); } boottime = wtmpdb_get_boottime (wtmpdb_path, &error); @@ -1172,21 +1231,29 @@ static int main_shutdown (int argc, char **argv) { struct option const longopts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, {"file", required_argument, NULL, 'f'}, {NULL, 0, NULL, '\0'} }; char *error = NULL; int c; - while ((c = getopt_long (argc, argv, "f:", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "f:hv", longopts, NULL)) != -1) { switch (c) { case 'f': wtmpdb_path = optarg; break; + case 'v': + show_version(); + break; + case 'h': + usage (EXIT_SUCCESS, CMD_SHUTDOWN); + break; default: - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_SHUTDOWN); break; } } @@ -1194,7 +1261,7 @@ main_shutdown (int argc, char **argv) if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_SHUTDOWN); } #if HAVE_AUDIT @@ -1239,20 +1306,28 @@ static int main_import (int argc, char **argv) { struct option const longopts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, {"file", required_argument, NULL, 'f'}, {NULL, 0, NULL, '\0'} }; int c; - while ((c = getopt_long (argc, argv, "f:", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "f:hv", longopts, NULL)) != -1) { switch (c) { case 'f': wtmpdb_path = optarg; break; + case 'v': + show_version(); + break; + case 'h': + usage (EXIT_SUCCESS, CMD_IMPORT); + break; default: - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_IMPORT); break; } } @@ -1260,7 +1335,7 @@ main_import (int argc, char **argv) if (argc == optind) { fprintf (stderr, "No files specified to import.\n"); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_IMPORT); } for (; optind < argc; optind++) @@ -1276,39 +1351,43 @@ main (int argc, char **argv) struct option const longopts[] = { {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, + {"file", required_argument, NULL, 'f'}, {NULL, 0, NULL, '\0'} }; int c; if (strcmp (basename(argv[0]), "last") == 0) return main_last (argc, argv); - else if (argc == 1) - usage (EXIT_SUCCESS); - else if (strcmp (argv[1], "last") == 0) + if (argc == 1) + usage (EXIT_SUCCESS, CMD_NONE); + if (strcmp (argv[1], cmd_name[CMD_LAST]) == 0) return main_last (--argc, ++argv); - else if (strcmp (argv[1], "boot") == 0) + if (strcmp (argv[1], cmd_name[CMD_BOOT]) == 0) return main_boot (--argc, ++argv); - else if (strcmp (argv[1], "shutdown") == 0) + if (strcmp (argv[1], cmd_name[CMD_SHUTDOWN]) == 0) return main_shutdown (--argc, ++argv); - else if (strcmp (argv[1], "boottime") == 0) + if (strcmp (argv[1], cmd_name[CMD_BOOTTIME]) == 0) return main_boottime (--argc, ++argv); - else if (strcmp (argv[1], "rotate") == 0) + if (strcmp (argv[1], cmd_name[CMD_ROTATE]) == 0) return main_rotate (--argc, ++argv); - else if (strcmp (argv[1], "import") == 0) + if (strcmp (argv[1], cmd_name[CMD_IMPORT]) == 0) return main_import (--argc, ++argv); - while ((c = getopt_long (argc, argv, "hv", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "f:hv", longopts, NULL)) != -1) { switch (c) { case 'h': - usage (EXIT_SUCCESS); + usage (EXIT_SUCCESS, CMD_NONE); break; case 'v': - printf ("wtmpdb %s\n", VERSION); + show_version(); + break; + case 'f': + /* ignore common option */ break; default: - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_NONE); break; } } @@ -1316,7 +1395,7 @@ main (int argc, char **argv) if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); - usage (EXIT_FAILURE); + usage (EXIT_FAILURE, CMD_NONE); } exit (EXIT_SUCCESS); From 2fe5b377c9a830e8d9225fa6e51782843d6f542e Mon Sep 17 00:00:00 2001 From: Jens Elkner Date: Thu, 30 Oct 2025 19:34:29 +0100 Subject: [PATCH 2/6] wtmpdb man page consolidation: common option -f ... --- man/wtmpdb.8.xml | 74 +++++++++++++----------------------------------- 1 file changed, 20 insertions(+), 54 deletions(-) diff --git a/man/wtmpdb.8.xml b/man/wtmpdb.8.xml index 1cd07ca..a814b86 100644 --- a/man/wtmpdb.8.xml +++ b/man/wtmpdb.8.xml @@ -81,16 +81,6 @@ - - - FILE - - - - Use FILE as wtmpdb database. - - - @@ -242,16 +232,6 @@ to the /var/lib/wtmpdb/wtmp.db database. boot options - - - FILE - - - - Use FILE as wtmpdb database. - - - @@ -264,6 +244,16 @@ + + + boottime + option + + + wtmpdb boottime shows the time of the + last boot. + + shutdown option @@ -273,17 +263,6 @@ requests to the /var/lib/wtmpdb/wtmp.db database. - shutdown options - - - FILE - - - - Use FILE as wtmpdb database. - - - @@ -296,17 +275,6 @@ database and removes these entries from the original one. rotate options - - - FILE - - - - Use FILE as wtmpdb database. - The exported DB file will be on the same location. - - - DAYS @@ -331,22 +299,20 @@ files to the /var/lib/wtmpdb/wtmp.db database. - import options - - - FILE - - - - Use FILE as wtmpdb database. - - - - global options + common options global options + + + FILE + + + Use FILE as wtmpdb + database. + + From a2ae9396f65d528e54b19a19e58b5e238625934b Mon Sep 17 00:00:00 2001 From: Jens Elkner Date: Thu, 30 Oct 2025 19:40:02 +0100 Subject: [PATCH 3/6] wtmpdb: session duration precision => seconds - per default show session duration with a precision of seconds - in legacy mode (option -L or if called as 'last') use minutes for backward compatibility --- man/wtmpdb.8.xml | 11 +++++++++++ src/wtmpdb.c | 24 ++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/man/wtmpdb.8.xml b/man/wtmpdb.8.xml index a814b86..c8090f4 100644 --- a/man/wtmpdb.8.xml +++ b/man/wtmpdb.8.xml @@ -101,6 +101,17 @@ + + + + + + + Display session duration with precision in minutes instead of seconds, + which is the default setting for the legacy 'last' command. + + + N diff --git a/src/wtmpdb.c b/src/wtmpdb.c index 64e1589..c1c5db4 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 legacy = 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 */ @@ -299,6 +300,16 @@ calc_time_length(char *dst, size_t dstlen, uint64_t start, uint64_t stop) int hours = (secs / 3600) % 24; uint64_t days = secs / 86400; + if (!legacy) { + secs %= 60; + if (days) + snprintf (dst, dstlen, "(%" PRId64 "+%02d:%02d:%02lu)", days, hours, mins, secs); + else if (hours) + snprintf (dst, dstlen, " (%02d:%02d:%02lu)", hours, mins, secs); + else + snprintf (dst, dstlen, " (00:%02d:%02lu)", mins, secs); + return; + } if (days) snprintf (dst, dstlen, "(%" PRId64 "+%02d:%02d)", days, hours, mins); else if (hours) @@ -694,6 +705,7 @@ usage (int retval, cmd_idx_t cmd) fputs (" -F, --fulltimes Display full times and dates\n", output); fputs (" -i, --ip Translate hostnames to IP addresses\n", output); fputs (" -j, --json Generate JSON output\n", output); + fputs (" -L, --legacy Session duration precision in minutes instead of seconds\n", output); fputs (" -n, --limit N, -N Display only first N entries\n", output); fputs (" -p, --present TIME Display who was present at TIME\n", output); fputs (" -R, --nohostname Don't display hostname\n", output); @@ -811,6 +823,7 @@ main_last (int argc, char **argv) {"fullnames", no_argument, NULL, 'w'}, {"fulltimes", no_argument, NULL, 'F'}, {"ip", no_argument, NULL, 'i'}, + {"legacy", no_argument, NULL, 'L'}, {"limit", required_argument, NULL, 'n'}, {"present", required_argument, NULL, 'p'}, {"nohostname", no_argument, NULL, 'R'}, @@ -826,7 +839,7 @@ main_last (int argc, char **argv) char *error = NULL; int c; - while ((c = getopt_long (argc, argv, "0123456789adf:Fhijn:p:RSs:t:vwx", + while ((c = getopt_long (argc, argv, "0123456789adf:FhijLn:p:RSs:t:vwx", longopts, NULL)) != -1) { switch (c) @@ -864,6 +877,9 @@ main_last (int argc, char **argv) case 'j': jflag = 1; break; + case 'L': + legacy = 1; + break; case 'n': maxentries = strtoul (optarg, NULL, 10); break; @@ -1356,7 +1372,11 @@ main (int argc, char **argv) }; int c; - if (strcmp (basename(argv[0]), "last") == 0) + if (strcmp (basename(argv[0]), "last") == 0) { + legacy = 1; + return main_last (argc, argv); + } + if (strcmp (basename(argv[0]), "wlast") == 0) return main_last (argc, argv); if (argc == 1) usage (EXIT_SUCCESS, CMD_NONE); From 56c0ebb20d4532b1626ababc188cba1376deefe2 Mon Sep 17 00:00:00 2001 From: Jens Elkner Date: Tue, 4 Nov 2025 03:08:47 +0100 Subject: [PATCH 4/6] fix #47: better lastlog - see https://github.com/thkukuk/wtmpdb/issues/47 - 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 | 19 +++++++++++++++++-- src/wtmpdbd.c | 10 ++++++++-- tests/tst-login-logout.c | 4 ++-- 11 files changed, 70 insertions(+), 20 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 c8090f4..a083380 100644 --- a/man/wtmpdb.8.xml +++ b/man/wtmpdb.8.xml @@ -176,6 +176,19 @@ + + + + + + + Display only the most recent log entry for each user. + This is the default when called as lastlog or wlastlog. + 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 c1c5db4..7760b33 100644 --- a/src/wtmpdb.c +++ b/src/wtmpdb.c @@ -81,6 +81,7 @@ static int jflag = 0; static int wflag = 0; static int xflag = 0; static int legacy = 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 */ @@ -712,6 +713,7 @@ usage (int retval, cmd_idx_t cmd) 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 FMT Display timestamps in the specified format.\n", output); @@ -830,6 +832,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'}, @@ -839,7 +842,7 @@ main_last (int argc, char **argv) char *error = NULL; int c; - while ((c = getopt_long (argc, argv, "0123456789adf:FhijLn:p:RSs:t:vwx", + while ((c = getopt_long (argc, argv, "0123456789adf:FhijLn:p:RSs:t:uvwx", longopts, NULL)) != -1) { switch (c) @@ -910,6 +913,9 @@ main_last (int argc, char **argv) exit (EXIT_FAILURE); } break; + case 'u': + uniq = 1; + break; case 'w': wflag = 1; break; @@ -966,7 +972,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) { @@ -1378,6 +1384,15 @@ main (int argc, char **argv) } if (strcmp (basename(argv[0]), "wlast") == 0) return main_last (argc, argv); + if (strcmp (basename(argv[0]), "lastlog") == 0) { + legacy = 1; + uniq = 1; + return main_last (argc, argv); + } + if (strcmp (basename(argv[0]), "wlastlog") == 0) { + uniq = 1; + return main_last (argc, argv); + } if (argc == 1) usage (EXIT_SUCCESS, CMD_NONE); if (strcmp (argv[1], cmd_name[CMD_LAST]) == 0) 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) { From 239068fe93cdca6ec434f489e200f973cc293ace Mon Sep 17 00:00:00 2001 From: Jens Elkner Date: Tue, 18 Nov 2025 15:14:00 +0100 Subject: [PATCH 5/6] implement option -o: show opensessions, only --- man/wtmpdb.8.xml | 10 ++++++++++ src/wtmpdb.c | 28 +++++++++++++++++++++------- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/man/wtmpdb.8.xml b/man/wtmpdb.8.xml index a083380..5479acb 100644 --- a/man/wtmpdb.8.xml +++ b/man/wtmpdb.8.xml @@ -125,6 +125,16 @@ + + + + + + + Display only sessions that are still open, i.e. no end of session has been recorded since last boot. + + + TIME diff --git a/src/wtmpdb.c b/src/wtmpdb.c index 7760b33..95554e8 100644 --- a/src/wtmpdb.c +++ b/src/wtmpdb.c @@ -69,7 +69,7 @@ static char *wtmpdb_path = NULL; #define LAST_TIMESTAMP_LEN 32 static uint64_t wtmp_start = UINT64_MAX; -static int after_reboot = 0; +static uint64_t after_reboot = 0; /* options for last */ static int hostlast = 0; @@ -82,6 +82,7 @@ static int wflag = 0; static int xflag = 0; static int legacy = 0; static int uniq = 0; +static int open_sessions = 0; /* show open sessions, only */ static const int name_len = 8; /* LAST_LOGIN_LEN */ static int login_fmt = TIMEFMT_SHORT; static int login_len = 16; /* 16 = short, 24 = full */ @@ -479,6 +480,15 @@ print_entry (void *unused __attribute__((__unused__)), || (endptr == argv[3]) || (*endptr != '\0')) fprintf (stderr, "Invalid numeric time entry for 'login': '%s'\n", argv[3]); + if (login_t < wtmp_start) + wtmp_start = login_t; + + /** Can't do it earlier because there is no contract that this function + * gets only invoked on lists sorted by Login time. Side effect is, that this + * way "wtmpdb begins ..." doesn't lie (as maxentries option does). + */ + if (open_sessions && after_reboot > login_t) + return 0; if (argv[4]) { @@ -489,9 +499,6 @@ print_entry (void *unused __attribute__((__unused__)), argv[4]); } - if (login_t < wtmp_start) - wtmp_start = login_t; - int swap = type == xflag && BOOT_TIME && logout_t != 0; if ((since && since > from_usec(swap ? logout_t : login_t)) || @@ -522,6 +529,8 @@ print_entry (void *unused __attribute__((__unused__)), if (logout_t != 0) { + if (open_sessions) + return 0; logout_t = strtoull(argv[4], &endptr, 10); if ((errno == ERANGE && logout_t == ULLONG_MAX) || (endptr == argv[4]) || (*endptr != '\0')) @@ -539,7 +548,7 @@ print_entry (void *unused __attribute__((__unused__)), } else /* login but no logout */ { - if (after_reboot) + if (after_reboot > login_t) { snprintf (times.logout, sizeof (times.logout), "crash"); times.length[0] = '\0'; @@ -583,7 +592,7 @@ print_entry (void *unused __attribute__((__unused__)), if (type == BOOT_TIME) { tty = "system boot"; - after_reboot = 1; + after_reboot = login_t; } char *print_service = NULL; @@ -708,6 +717,7 @@ usage (int retval, cmd_idx_t cmd) fputs (" -j, --json Generate JSON output\n", output); fputs (" -L, --legacy Session duration precision in minutes instead of seconds\n", output); fputs (" -n, --limit N, -N Display only first N entries\n", output); + fputs (" -o, --open Display open sessions, only.\n", output); fputs (" -p, --present TIME Display who was present at TIME\n", output); fputs (" -R, --nohostname Don't display hostname\n", output); fputs (" -S, --service Display PAM service used to login\n", output); @@ -827,6 +837,7 @@ main_last (int argc, char **argv) {"ip", no_argument, NULL, 'i'}, {"legacy", no_argument, NULL, 'L'}, {"limit", required_argument, NULL, 'n'}, + {"open", no_argument, NULL, 'o'}, {"present", required_argument, NULL, 'p'}, {"nohostname", no_argument, NULL, 'R'}, {"service", no_argument, NULL, 'S'}, @@ -842,7 +853,7 @@ main_last (int argc, char **argv) char *error = NULL; int c; - while ((c = getopt_long (argc, argv, "0123456789adf:FhijLn:p:RSs:t:uvwx", + while ((c = getopt_long (argc, argv, "0123456789adf:FhijLn:op:RSs:t:uvwx", longopts, NULL)) != -1) { switch (c) @@ -886,6 +897,9 @@ main_last (int argc, char **argv) case 'n': maxentries = strtoul (optarg, NULL, 10); break; + case 'o': + open_sessions = 1; + break; case 'p': if (parse_time (optarg, &present) < 0) { From 9f7ac3cd98c16006ce4621a3888c75e5c55da733 Mon Sep 17 00:00:00 2001 From: Jens Elkner Date: Sun, 30 Nov 2025 02:58:18 +0100 Subject: [PATCH 6/6] last: add --compact option for concise output Introduce a new --compact (-c) option for the `last` command. When enabled: - The logout timestamp column is hidden. - The login datetime defaults to YYYY-mm-dd HH:MM:SS. This reduces redundant information in the output, makes the output more precise and easier to read and parse. Fixes https://github.com/thkukuk/wtmpdb/issues/53 --- man/wtmpdb.8.xml | 32 +++++++++++++++----- src/wtmpdb.c | 78 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 86 insertions(+), 24 deletions(-) diff --git a/man/wtmpdb.8.xml b/man/wtmpdb.8.xml index 5479acb..cf45398 100644 --- a/man/wtmpdb.8.xml +++ b/man/wtmpdb.8.xml @@ -71,6 +71,20 @@ + + + + + + + Hide the logout column and set the time format for the login + column to compact. One may still overwrite the + time format by using the option after + this one. If the environment variable LAST_COMPACT + is set, this option is applied before any others are processed. + + + @@ -228,14 +242,16 @@ Display timestamps in the specified FORMAT. The format can be - notime, - short, - full, or - iso. - notime will not display times at - all, short is the default option, - full will display the full times - and dates, and iso will display + notime, + short, + compact, + full, or + iso. + notime will not display times at + all, short is the default option, + compact format is "YYYY-mm-dd HH:MM:SS", + full will display the full times + and dates, and iso will display times in ISO-8601 format. diff --git a/src/wtmpdb.c b/src/wtmpdb.c index 95554e8..57adb2d 100644 --- a/src/wtmpdb.c +++ b/src/wtmpdb.c @@ -60,6 +60,7 @@ static char *wtmpdb_path = NULL; #define TIMEFMT_HHMM 3 #define TIMEFMT_NOTIME 4 #define TIMEFMT_ISO 5 +#define TIMEFMT_COMPACT 6 #define TIMEFMT_VALUE 255 @@ -82,6 +83,7 @@ static int wflag = 0; static int xflag = 0; static int legacy = 0; static int uniq = 0; +static int compact = 0; static int open_sessions = 0; /* show open sessions, only */ static const int name_len = 8; /* LAST_LOGIN_LEN */ static int login_fmt = TIMEFMT_SHORT; @@ -249,6 +251,14 @@ time_format (const char *fmt) logout_len = 25; return TIMEFMT_ISO; } + if (strcmp (fmt, "compact") == 0) + { + login_fmt = TIMEFMT_COMPACT; + login_len = 19; + logout_fmt = TIMEFMT_COMPACT; + logout_len = 19; + return TIMEFMT_COMPACT; + } return -1; } @@ -286,6 +296,13 @@ format_time (int fmt, char *dst, size_t dstlen, uint64_t time) strftime (dst, dstlen, "%FT%T%z", tm); /* Same ISO8601 format original last command uses */ break; } + case TIMEFMT_COMPACT: + { + time_t t = (time_t)time; + struct tm *tm = localtime (&t); + strftime (dst, dstlen, "%F %T", tm); + break; + } case TIMEFMT_NOTIME: *dst = '\0'; break; @@ -378,25 +395,27 @@ print_line (const char *user, const char *tty, const char *host, if (print_service && strlen (print_service) > 0) printf (" \"service\": \"%s\",\n", print_service); printf (" \"login\": \"%s\",\n", logintime); + if (!compact) + printf (" \"logout\": \"%s\",\n", logouttime); if (length[0] == ' ' || length[0] == '(') { - printf (" \"logout\": \"%s\",\n", logouttime); printf (" \"length\": \"%s\"\n", remove_parentheses(length)); } else - printf (" \"logout\": \"%s %s\"\n", logouttime, length); + printf (" \"length\": \"%s\"\n", length); printf (" }"); } else { char *line; + const char *sep = compact ? "" : " - "; if (nohostname) { - if (asprintf (&line, "%-8.*s %-12.12s%s %-*.*s - %-*.*s %s\n", + if (asprintf (&line, "%-8.*s %-12.12s%s %-*.*s%s%-*.*s %s\n", wflag?(int)strlen (user):name_len, map_soft_reboot (user), tty, print_service, - login_len, login_len, logintime, + login_len, login_len, logintime, sep, logout_len, logout_len, logouttime, length) < 0) { @@ -408,10 +427,10 @@ print_line (const char *user, const char *tty, const char *host, { if (hostlast) { - if (asprintf (&line, "%-8.*s %-12.12s%s %-*.*s - %-*.*s %-12.12s %s\n", + if (asprintf (&line, "%-8.*s %-12.12s%s %-*.*s%s%-*.*s %-12.12s %s\n", wflag?(int)strlen(user):name_len, map_soft_reboot (user), tty, print_service, - login_len, login_len, logintime, + login_len, login_len, logintime, sep, logout_len, logout_len, logouttime, length, host) < 0) { @@ -421,10 +440,10 @@ print_line (const char *user, const char *tty, const char *host, } else { - if (asprintf (&line, "%-8.*s %-12.12s %-16.*s%s %-*.*s - %-*.*s %s\n", + if (asprintf (&line, "%-8.*s %-12.12s %-16.*s%s %-*.*s%s%-*.*s %s\n", wflag?(int)strlen(user):name_len, map_soft_reboot (user), tty, wflag?(int)strlen(host):host_len, host, print_service, - login_len, login_len, logintime, + login_len, login_len, logintime, sep, logout_len, logout_len, logouttime, length) < 0) { @@ -540,15 +559,22 @@ print_entry (void *unused __attribute__((__unused__)), if (present && (0 < (logout_t/USEC_PER_SEC)) && ((time_t)(logout_t/USEC_PER_SEC) < present)) return 0; - - format_time (logout_fmt, times.logout, sizeof (times.logout), + if (compact) { + times.logout[0] = '\0'; + } else { + format_time (logout_fmt, times.logout, sizeof (times.logout), logout_t/USEC_PER_SEC); - + } calc_time_length (times.length, sizeof(times.length), login_t, logout_t); } else /* login but no logout */ { - if (after_reboot > login_t) + if (compact) { + times.logout[0] = '\0'; + times.length[1] = '\0'; + times.length[0] = (after_reboot > login_t) ? '?' : '.'; + } + else if (after_reboot > login_t) { snprintf (times.logout, sizeof (times.logout), "crash"); times.length[0] = '\0'; @@ -657,8 +683,12 @@ print_entry (void *unused __attribute__((__unused__)), format_time (login_fmt, shutdown.login, sizeof (shutdown.login), logout_t/USEC_PER_SEC); - format_time (logout_fmt, shutdown.logout, sizeof (shutdown.logout), - newer_boot/USEC_PER_SEC); + if (compact) { + shutdown.logout[0] = '\0'; + } else { + format_time (logout_fmt, shutdown.logout, sizeof (shutdown.logout), + newer_boot/USEC_PER_SEC); + } calc_time_length (shutdown.length, sizeof(shutdown.length), logout_t, newer_boot); if ((!until || until >= from_usec(logout_t)) && @@ -711,6 +741,7 @@ usage (int retval, cmd_idx_t cmd) fprintf (output, "\nOptions for %s:\n", cmd_name[CMD_LAST]); if (cmd == CMD_LAST || cmd == CMD_NONE) { fputs (" -a, --hostlast Display hostnames as last entry\n", output); + fputs (" -c, --compact Hide logouts and set login time format to 'compact'\n", output); fputs (" -d, --dns Translate IP addresses into a hostname\n", output); fputs (" -F, --fulltimes Display full times and dates\n", output); fputs (" -i, --ip Translate hostnames to IP addresses\n", output); @@ -727,7 +758,7 @@ usage (int retval, cmd_idx_t cmd) 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 FMT Display timestamps in the specified format.\n", output); - fputs ("\n FMT format: notime|short|full|iso\n", output); + fputs ("\n FMT format: notime|short|full|iso|compact\n", output); fputs (" TIME format: YYYY-MM-DD HH:MM:SS\n", output); } if (cmd == CMD_NONE) @@ -830,6 +861,7 @@ main_last (int argc, char **argv) {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {"hostlast", no_argument, NULL, 'a'}, + {"compact", no_argument, NULL, 'c'}, {"dns", no_argument, NULL, 'd'}, {"file", required_argument, NULL, 'f'}, {"fullnames", no_argument, NULL, 'w'}, @@ -853,7 +885,12 @@ main_last (int argc, char **argv) char *error = NULL; int c; - while ((c = getopt_long (argc, argv, "0123456789adf:FhijLn:op:RSs:t:uvwx", + if (getenv("LAST_COMPACT")) { + compact = 1; + time_fmt = time_format("compact"); + logout_len = 0; + } + while ((c = getopt_long (argc, argv, "0123456789acdf:FhijLn:op:RSs:t:uvwx", longopts, NULL)) != -1) { switch (c) @@ -873,6 +910,11 @@ main_last (int argc, char **argv) case 'a': hostlast = 1; break; + case 'c': + compact = 1; + time_fmt = time_format("compact"); + logout_len = 0; + break; case 'd': dflag = 1; break; @@ -884,6 +926,7 @@ main_last (int argc, char **argv) login_len = 24; logout_fmt = TIMEFMT_CTIME; logout_len = 24; + compact = 0; break; case 'i': iflag = 1; @@ -959,6 +1002,9 @@ main_last (int argc, char **argv) if (argc > optind) match = argv + optind; + if (compact) + logout_len = 0; + if (nohostname && hostlast) { fprintf (stderr, "The options -a and -R cannot be used together.\n");