last: optional alternative sysv-like implementation
authorDenis Vlasenko <vda.linux@googlemail.com>
Thu, 22 May 2008 02:07:58 +0000 (02:07 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Thu, 22 May 2008 02:07:58 +0000 (02:07 -0000)
(by Patricia Muscalu <patricia.muscalu AT axis.com>)

function                                             old     new   delta
last_main                                            448     917    +469
show_entry                                             -     319    +319
packed_usage                                       24216   24268     +52
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 2/0 up/down: 840/0)             Total: 840 bytes

include/usage.h
miscutils/Config.in
miscutils/last.c
miscutils/last_fancy.c [new file with mode: 0644]

index cffe7822035b8a11fa9ebac01c00455987db798f..1f4213a2e9dc219aa575574c9c33808bcd25001f 100644 (file)
        "lash is deprecated, please use hush"
 
 #define last_trivial_usage \
-       ""
+       ""USE_FEATURE_LAST_FANCY("[-HW] [-f file]")
 #define last_full_usage "\n\n" \
-       "Show listing of the last users that logged into the system"
+       "Show listing of the last users that logged into the system" \
+       USE_FEATURE_LAST_FANCY( "\n" \
+     "\nOptions:" \
+     "\n       -H              Show header line" \
+     "\n       -W              Display with no host column truncation" \
+     "\n       -f file         Read from file instead of /var/log/wtmp" \
+       )
 
 #define sha1sum_trivial_usage \
        "[OPTION] [FILEs...]" \
index c4eb1083878c66e3c378e1514ef7ba3c88dc72bd..427906ff8f4a79432334516431091c0141d2d6ae 100644 (file)
@@ -229,6 +229,14 @@ config LAST
        help
          'last' displays a list of the last users that logged into the system.
 
+config FEATURE_LAST_FANCY
+       bool "Fancy output"
+       default n
+       depends on LAST
+       help
+         'last' displays detailed information about the last users that
+         logged into the system (mimics sysvinit last). +900 bytes.
+
 config LESS
        bool "less"
        default n
index 749862c358fe170b555c88e89a0fb47d687d625a..ef41444c576f3c6cca3b1fec3f059ad97f106448 100644 (file)
 #error struct utmp member char[] size(s) have changed!
 #endif
 
+#if ENABLE_FEATURE_LAST_FANCY
+
+#include "last_fancy.c"
+
+#else
+
 int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int last_main(int argc, char **argv ATTRIBUTE_UNUSED)
 {
@@ -92,3 +98,5 @@ int last_main(int argc, char **argv ATTRIBUTE_UNUSED)
 
        fflush_stdout_and_exit(EXIT_SUCCESS);
 }
+
+#endif
diff --git a/miscutils/last_fancy.c b/miscutils/last_fancy.c
new file mode 100644 (file)
index 0000000..0ad2b92
--- /dev/null
@@ -0,0 +1,283 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * (sysvinit like) last implementation
+ *
+ * Copyright (C) 2008 by Patricia Muscalu <patricia.muscalu@axis.com>
+ *
+ * Licensed under the GPLv2 or later, see the file LICENSE in this tarball.
+ */
+
+#define HEADER_FORMAT     "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %-12.12s\n"
+#define HEADER_LINE       "USER", "TTY", \
+       INET_ADDRSTRLEN, INET_ADDRSTRLEN, "HOST", "LOGIN", "  TIME", ""
+#define HEADER_LINE_WIDE  "USER", "TTY", \
+       INET6_ADDRSTRLEN, INET6_ADDRSTRLEN, "HOST", "LOGIN", "  TIME", ""
+
+enum {
+       NORMAL,
+       LOGGED,
+       DOWN,
+       REBOOT,
+       CRASH,
+       GONE
+};
+
+enum {
+       LAST_OPT_W = (1 << 0),  /* -W wide            */
+       LAST_OPT_f = (1 << 1),  /* -f input file      */
+       LAST_OPT_H = (1 << 2),  /* -H header          */
+};
+
+#define show_wide (option_mask32 & LAST_OPT_W)
+
+static void show_entry(struct utmp *ut, int state, time_t dur_secs)
+{
+       unsigned days, hours, mins;
+       char duration[32];
+       char login_time[17];
+       char logout_time[8];
+       const char *logout_str;
+       const char *duration_str;
+
+       safe_strncpy(login_time, ctime(&(ut->ut_time)), 17);
+       snprintf(logout_time, 8, "- %s", ctime(&dur_secs) + 11);
+
+       dur_secs = MAX(dur_secs - (time_t)ut->ut_tv.tv_sec, (time_t)0);
+       /* unsigned int is easier to divide than time_t (which may be signed long) */
+       mins = dur_secs / 60;
+       days = mins / (24*60);
+       mins = mins % (24*60);
+       hours = mins / 60;
+       mins = mins % 60;
+
+       if (days) {
+               sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
+       } else {
+               sprintf(duration, " (%02u:%02u)", hours, mins);
+       }
+
+       logout_str = logout_time;
+       duration_str = duration;
+       switch (state) {
+       case NORMAL:
+               break;
+       case LOGGED:
+               logout_str = "  still";
+               duration_str = "logged in";
+               break;
+       case DOWN:
+               logout_str = "- down ";
+               break;
+       case REBOOT:
+               break;
+       case CRASH:
+               logout_str = "- crash";
+               break;
+       case GONE:
+               logout_str = "   gone";
+               duration_str = "- no logout";
+               break;
+       }
+
+       printf(HEADER_FORMAT,
+                  ut->ut_name,
+                  ut->ut_line,
+                  show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
+                  show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
+                  ut->ut_host,
+                  login_time,
+                  logout_str,
+                  duration_str);
+}
+
+static int get_ut_type(struct utmp *ut)
+{
+       if (ut->ut_line[0] == '~') {
+               if (strncmp(ut->ut_user, "shutdown", sizeof("shutdown")-1) == 0) {
+                       return SHUTDOWN_TIME;
+               }
+               if (strncmp(ut->ut_user, "reboot", sizeof("reboot")-1) == 0) {
+                       return BOOT_TIME;
+               }
+               if (strncmp(ut->ut_user, "runlevel", sizeof("runlevel")-1) == 0) {
+                       return RUN_LVL;
+               }
+               return ut->ut_type;
+       }
+
+       if (ut->ut_name[0] == 0) {
+               return DEAD_PROCESS;
+       }
+
+       if ((ut->ut_type != DEAD_PROCESS)
+        && (strcmp(ut->ut_name, "LOGIN") != 0)
+        && ut->ut_name[0]
+        && ut->ut_line[0]
+       ) {
+               ut->ut_type = USER_PROCESS;
+       }
+
+       if (strcmp(ut->ut_name, "date") == 0) {
+               if (ut->ut_line[0] == '|') {
+                       return OLD_TIME;
+               }
+               if (ut->ut_line[0] == '{') {
+                       return NEW_TIME;
+               }
+       }
+       return ut->ut_type;
+}
+
+static int is_runlevel_shutdown(struct utmp *ut)
+{
+       if (((ut->ut_pid & 255) == '0') || ((ut->ut_pid & 255) == '6')) {
+               return 1;
+       }
+
+       return 0;
+}
+
+int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int last_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       struct utmp ut;
+       const char *filename = _PATH_WTMP;
+       llist_t *zlist;
+       off_t pos;
+       time_t start_time;
+       time_t boot_time;
+       time_t down_time;
+       int file;
+       unsigned opt;
+       smallint going_down;
+       smallint boot_down;
+
+       opt = getopt32(argv, "Wf:" /* "H" */, &filename);
+#ifdef BUT_UTIL_LINUX_LAST_HAS_NO_SUCH_OPT
+       if (opt & LAST_OPT_H) {
+               /* Print header line */
+               if (opt & LAST_OPT_W) {
+                       printf(HEADER_FORMAT, HEADER_LINE_WIDE);
+               } else {
+                       printf(HEADER_FORMAT, HEADER_LINE);
+               }
+       }
+#endif
+
+       file = xopen(filename, O_RDONLY);
+       if (full_read(file, &ut, sizeof(ut)) != sizeof(ut)) {
+               struct stat st;
+               fstat(file, &st);
+               start_time = st.st_ctime;
+               goto exit;
+       }
+       start_time = ut.ut_time;
+
+       time(&down_time);
+       going_down = 0;
+       boot_down = NORMAL; /* 0 */
+       zlist = NULL;
+       boot_time = 0;
+       pos = 0;
+       for (;;) {
+               pos -= (off_t)sizeof(ut);
+               /* Bug? What if file changes size?
+                * What if size is not a multiple of sizeof(ut)? */
+               if (lseek(file, pos, SEEK_END) < 0) {
+                       /* Beyond the beginning of the file boundary =>
+                        * the whole file has been read. */
+                       break;
+               }
+               if (full_read(file, &ut, sizeof(ut)) != sizeof(ut))
+                       break;
+
+               switch (get_ut_type(&ut)) {
+               case SHUTDOWN_TIME:
+                       down_time = ut.ut_time;
+                       boot_down = DOWN;
+                       going_down = 1;
+                       break;
+               case RUN_LVL:
+                       if (is_runlevel_shutdown(&ut)) {
+                               down_time = ut.ut_time;
+                               going_down = 1;
+                               boot_down = DOWN;
+                       }
+                       break;
+               case BOOT_TIME:
+                       strcpy(ut.ut_line, "system boot");
+                       show_entry(&ut, REBOOT, down_time);
+                       boot_down = CRASH;
+                       going_down = 1;
+                       break;
+               case DEAD_PROCESS:
+                       if (!ut.ut_line[0]) {
+                               break;
+                       }
+                       /* add_entry */
+                       llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
+                       break;
+               case USER_PROCESS: {
+                       int show;
+
+                       if (!ut.ut_line[0]) {
+                               break;
+                       }
+                       /* find_entry */
+                       show = 1;
+                       {
+                               llist_t *el, *next;
+                               for (el = zlist; el; el = next) {
+                                       struct utmp *up = (struct utmp *)el->data;
+                                       next = el->link;
+                                       if (strncmp(up->ut_line, ut.ut_line, UT_LINESIZE) == 0) {
+                                               if (show) {
+                                                       show_entry(&ut, NORMAL, up->ut_time);
+                                                       show = 0;
+                                               }
+                                               llist_unlink(&zlist, el);
+                                               free(el->data);
+                                               free(el);
+                                       }
+                               }
+                       }
+
+                       if (show) {
+                               int state = boot_down;
+
+                               if (boot_time == 0) {
+                                       state = LOGGED;
+                                       /* Check if the process is alive */
+                                       if ((ut.ut_pid > 0)
+                                        && (kill(ut.ut_pid, 0) != 0)
+                                        && (errno == ESRCH)) {
+                                               state = GONE;
+                                       }
+                               }
+                               show_entry(&ut, state, boot_time);
+                       }
+                       /* add_entry */
+                       llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
+                       break;
+               }
+               }
+
+               if (going_down) {
+                       boot_time = ut.ut_time;
+                       llist_free(zlist, free);
+                       zlist = NULL;
+                       going_down = 0;
+               }
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               llist_free(zlist, free);
+       }
+
+ exit:
+       printf("\nwtmp begins %s", ctime(&start_time));
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(file);
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}