/* vi: set sw=4 ts=4: */ /* * (sysvinit like) last implementation * * Copyright (C) 2008 by Patricia Muscalu <patricia.muscalu@axis.com> * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ #include "libbb.h" #include <utmp.h> /* NB: ut_name and ut_user are the same field, use only one name (ut_user) * to reduce confusion */ #ifndef SHUTDOWN_TIME # define SHUTDOWN_TIME 254 #endif #define HEADER_FORMAT "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\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; time_t tmp; /* manpages say ut_tv.tv_sec *is* time_t, * but some systems have it wrong */ tmp = ut->ut_tv.tv_sec; safe_strncpy(login_time, ctime(&tmp), 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_user, 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 (strcmp(ut->ut_user, "shutdown") == 0) { return SHUTDOWN_TIME; } if (strcmp(ut->ut_user, "reboot") == 0) { return BOOT_TIME; } if (strcmp(ut->ut_user, "runlevel") == 0) { return RUN_LVL; } return ut->ut_type; } if (ut->ut_user[0] == 0) { return DEAD_PROCESS; } if ((ut->ut_type != DEAD_PROCESS) && (strcmp(ut->ut_user, "LOGIN") != 0) && ut->ut_user[0] && ut->ut_line[0] ) { ut->ut_type = USER_PROCESS; } if (strcmp(ut->ut_user, "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 UNUSED_PARAM, 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); { /* in case the file is empty... */ struct stat st; fstat(file, &st); start_time = st.st_ctime; } time(&down_time); going_down = 0; boot_down = NORMAL; /* 0 */ zlist = NULL; boot_time = 0; /* get file size, rounding down to last full record */ pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut); for (;;) { pos -= (off_t)sizeof(ut); if (pos < 0) { /* Beyond the beginning of the file boundary => * the whole file has been read. */ break; } xlseek(file, pos, SEEK_SET); xread(file, &ut, sizeof(ut)); /* rewritten by each record, eventially will have * first record's ut_tv.tv_sec: */ start_time = ut.ut_tv.tv_sec; switch (get_ut_type(&ut)) { case SHUTDOWN_TIME: down_time = ut.ut_tv.tv_sec; boot_down = DOWN; going_down = 1; break; case RUN_LVL: if (is_runlevel_shutdown(&ut)) { down_time = ut.ut_tv.tv_sec; 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_tv.tv_sec); 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_tv.tv_sec; llist_free(zlist, free); zlist = NULL; going_down = 0; } } if (ENABLE_FEATURE_CLEAN_UP) { llist_free(zlist, free); } printf("\nwtmp begins %s", ctime(&start_time)); if (ENABLE_FEATURE_CLEAN_UP) close(file); fflush_stdout_and_exit(EXIT_SUCCESS); }