busybox/miscutils/last_fancy.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * (sysvinit like) last implementation
   4 *
   5 * Copyright (C) 2008 by Patricia Muscalu <patricia.muscalu@axis.com>
   6 *
   7 * Licensed under the GPLv2 or later, see the file LICENSE in this tarball.
   8 */
   9
  10#include "libbb.h"
  11#include <utmp.h>
  12
  13/* NB: ut_name and ut_user are the same field, use only one name (ut_user)
  14 * to reduce confusion */
  15
  16#ifndef SHUTDOWN_TIME
  17#  define SHUTDOWN_TIME 254
  18#endif
  19
  20#define HEADER_FORMAT     "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n"
  21#define HEADER_LINE       "USER", "TTY", \
  22        INET_ADDRSTRLEN, INET_ADDRSTRLEN, "HOST", "LOGIN", "  TIME", ""
  23#define HEADER_LINE_WIDE  "USER", "TTY", \
  24        INET6_ADDRSTRLEN, INET6_ADDRSTRLEN, "HOST", "LOGIN", "  TIME", ""
  25
  26enum {
  27        NORMAL,
  28        LOGGED,
  29        DOWN,
  30        REBOOT,
  31        CRASH,
  32        GONE
  33};
  34
  35enum {
  36        LAST_OPT_W = (1 << 0),  /* -W wide            */
  37        LAST_OPT_f = (1 << 1),  /* -f input file      */
  38        LAST_OPT_H = (1 << 2),  /* -H header          */
  39};
  40
  41#define show_wide (option_mask32 & LAST_OPT_W)
  42
  43static void show_entry(struct utmp *ut, int state, time_t dur_secs)
  44{
  45        unsigned days, hours, mins;
  46        char duration[32];
  47        char login_time[17];
  48        char logout_time[8];
  49        const char *logout_str;
  50        const char *duration_str;
  51        time_t tmp;
  52
  53        /* manpages say ut_tv.tv_sec *is* time_t,
  54         * but some systems have it wrong */
  55        tmp = ut->ut_tv.tv_sec;
  56        safe_strncpy(login_time, ctime(&tmp), 17);
  57        snprintf(logout_time, 8, "- %s", ctime(&dur_secs) + 11);
  58
  59        dur_secs = MAX(dur_secs - (time_t)ut->ut_tv.tv_sec, (time_t)0);
  60        /* unsigned int is easier to divide than time_t (which may be signed long) */
  61        mins = dur_secs / 60;
  62        days = mins / (24*60);
  63        mins = mins % (24*60);
  64        hours = mins / 60;
  65        mins = mins % 60;
  66
  67//      if (days) {
  68                sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
  69//      } else {
  70//              sprintf(duration, " (%02u:%02u)", hours, mins);
  71//      }
  72
  73        logout_str = logout_time;
  74        duration_str = duration;
  75        switch (state) {
  76        case NORMAL:
  77                break;
  78        case LOGGED:
  79                logout_str = "  still";
  80                duration_str = "logged in";
  81                break;
  82        case DOWN:
  83                logout_str = "- down ";
  84                break;
  85        case REBOOT:
  86                break;
  87        case CRASH:
  88                logout_str = "- crash";
  89                break;
  90        case GONE:
  91                logout_str = "   gone";
  92                duration_str = "- no logout";
  93                break;
  94        }
  95
  96        printf(HEADER_FORMAT,
  97                   ut->ut_user,
  98                   ut->ut_line,
  99                   show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
 100                   show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
 101                   ut->ut_host,
 102                   login_time,
 103                   logout_str,
 104                   duration_str);
 105}
 106
 107static int get_ut_type(struct utmp *ut)
 108{
 109        if (ut->ut_line[0] == '~') {
 110                if (strcmp(ut->ut_user, "shutdown") == 0) {
 111                        return SHUTDOWN_TIME;
 112                }
 113                if (strcmp(ut->ut_user, "reboot") == 0) {
 114                        return BOOT_TIME;
 115                }
 116                if (strcmp(ut->ut_user, "runlevel") == 0) {
 117                        return RUN_LVL;
 118                }
 119                return ut->ut_type;
 120        }
 121
 122        if (ut->ut_user[0] == 0) {
 123                return DEAD_PROCESS;
 124        }
 125
 126        if ((ut->ut_type != DEAD_PROCESS)
 127         && (strcmp(ut->ut_user, "LOGIN") != 0)
 128         && ut->ut_user[0]
 129         && ut->ut_line[0]
 130        ) {
 131                ut->ut_type = USER_PROCESS;
 132        }
 133
 134        if (strcmp(ut->ut_user, "date") == 0) {
 135                if (ut->ut_line[0] == '|') {
 136                        return OLD_TIME;
 137                }
 138                if (ut->ut_line[0] == '{') {
 139                        return NEW_TIME;
 140                }
 141        }
 142        return ut->ut_type;
 143}
 144
 145static int is_runlevel_shutdown(struct utmp *ut)
 146{
 147        if (((ut->ut_pid & 255) == '0') || ((ut->ut_pid & 255) == '6')) {
 148                return 1;
 149        }
 150
 151        return 0;
 152}
 153
 154int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 155int last_main(int argc UNUSED_PARAM, char **argv)
 156{
 157        struct utmp ut;
 158        const char *filename = _PATH_WTMP;
 159        llist_t *zlist;
 160        off_t pos;
 161        time_t start_time;
 162        time_t boot_time;
 163        time_t down_time;
 164        int file;
 165        unsigned opt;
 166        smallint going_down;
 167        smallint boot_down;
 168
 169        opt = getopt32(argv, "Wf:" /* "H" */, &filename);
 170#ifdef BUT_UTIL_LINUX_LAST_HAS_NO_SUCH_OPT
 171        if (opt & LAST_OPT_H) {
 172                /* Print header line */
 173                if (opt & LAST_OPT_W) {
 174                        printf(HEADER_FORMAT, HEADER_LINE_WIDE);
 175                } else {
 176                        printf(HEADER_FORMAT, HEADER_LINE);
 177                }
 178        }
 179#endif
 180
 181        file = xopen(filename, O_RDONLY);
 182        {
 183                /* in case the file is empty... */
 184                struct stat st;
 185                fstat(file, &st);
 186                start_time = st.st_ctime;
 187        }
 188
 189        time(&down_time);
 190        going_down = 0;
 191        boot_down = NORMAL; /* 0 */
 192        zlist = NULL;
 193        boot_time = 0;
 194        /* get file size, rounding down to last full record */
 195        pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut);
 196        for (;;) {
 197                pos -= (off_t)sizeof(ut);
 198                if (pos < 0) {
 199                        /* Beyond the beginning of the file boundary =>
 200                         * the whole file has been read. */
 201                        break;
 202                }
 203                xlseek(file, pos, SEEK_SET);
 204                xread(file, &ut, sizeof(ut));
 205                /* rewritten by each record, eventially will have
 206                 * first record's ut_tv.tv_sec: */
 207                start_time = ut.ut_tv.tv_sec;
 208
 209                switch (get_ut_type(&ut)) {
 210                case SHUTDOWN_TIME:
 211                        down_time = ut.ut_tv.tv_sec;
 212                        boot_down = DOWN;
 213                        going_down = 1;
 214                        break;
 215                case RUN_LVL:
 216                        if (is_runlevel_shutdown(&ut)) {
 217                                down_time = ut.ut_tv.tv_sec;
 218                                going_down = 1;
 219                                boot_down = DOWN;
 220                        }
 221                        break;
 222                case BOOT_TIME:
 223                        strcpy(ut.ut_line, "system boot");
 224                        show_entry(&ut, REBOOT, down_time);
 225                        boot_down = CRASH;
 226                        going_down = 1;
 227                        break;
 228                case DEAD_PROCESS:
 229                        if (!ut.ut_line[0]) {
 230                                break;
 231                        }
 232                        /* add_entry */
 233                        llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
 234                        break;
 235                case USER_PROCESS: {
 236                        int show;
 237
 238                        if (!ut.ut_line[0]) {
 239                                break;
 240                        }
 241                        /* find_entry */
 242                        show = 1;
 243                        {
 244                                llist_t *el, *next;
 245                                for (el = zlist; el; el = next) {
 246                                        struct utmp *up = (struct utmp *)el->data;
 247                                        next = el->link;
 248                                        if (strncmp(up->ut_line, ut.ut_line, UT_LINESIZE) == 0) {
 249                                                if (show) {
 250                                                        show_entry(&ut, NORMAL, up->ut_tv.tv_sec);
 251                                                        show = 0;
 252                                                }
 253                                                llist_unlink(&zlist, el);
 254                                                free(el->data);
 255                                                free(el);
 256                                        }
 257                                }
 258                        }
 259
 260                        if (show) {
 261                                int state = boot_down;
 262
 263                                if (boot_time == 0) {
 264                                        state = LOGGED;
 265                                        /* Check if the process is alive */
 266                                        if ((ut.ut_pid > 0)
 267                                         && (kill(ut.ut_pid, 0) != 0)
 268                                         && (errno == ESRCH)) {
 269                                                state = GONE;
 270                                        }
 271                                }
 272                                show_entry(&ut, state, boot_time);
 273                        }
 274                        /* add_entry */
 275                        llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
 276                        break;
 277                }
 278                }
 279
 280                if (going_down) {
 281                        boot_time = ut.ut_tv.tv_sec;
 282                        llist_free(zlist, free);
 283                        zlist = NULL;
 284                        going_down = 0;
 285                }
 286        }
 287
 288        if (ENABLE_FEATURE_CLEAN_UP) {
 289                llist_free(zlist, free);
 290        }
 291
 292        printf("\nwtmp begins %s", ctime(&start_time));
 293
 294        if (ENABLE_FEATURE_CLEAN_UP)
 295                close(file);
 296        fflush_stdout_and_exit(EXIT_SUCCESS);
 297}
 298