diff options
-rw-r--r-- | include/usage.h | 10 | ||||
-rw-r--r-- | miscutils/Config.in | 8 | ||||
-rw-r--r-- | miscutils/last.c | 8 | ||||
-rw-r--r-- | miscutils/last_fancy.c | 283 |
4 files changed, 307 insertions, 2 deletions
diff --git a/include/usage.h b/include/usage.h index cffe78220..1f4213a2e 100644 --- a/include/usage.h +++ b/include/usage.h @@ -3504,9 +3504,15 @@ "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...]" \ diff --git a/miscutils/Config.in b/miscutils/Config.in index c4eb10838..427906ff8 100644 --- a/miscutils/Config.in +++ b/miscutils/Config.in @@ -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 diff --git a/miscutils/last.c b/miscutils/last.c index 749862c35..ef41444c5 100644 --- a/miscutils/last.c +++ b/miscutils/last.c @@ -26,6 +26,12 @@ #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 index 000000000..0ad2b9245 --- /dev/null +++ b/miscutils/last_fancy.c @@ -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); +} |