1/* last.c - Show listing of last logged in users.
2 *
3 * Copyright 2013 Ranjan Kumar <ranjankumar.bth@gmail.com>
4 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
5 *
6 * No Standard.
7
8USE_LAST(NEWTOY(last, "f:W", TOYFLAG_BIN))
9
10config LAST
11  bool "last"
12  default n
13  help
14    usage: last [-W] [-f FILE]
15
16    Show listing of last logged in users.
17
18    -W      Display the information without host-column truncation.
19    -f FILE Read from file FILE instead of /var/log/wtmp.
20*/
21
22#define FOR_last
23#include "toys.h"
24#include <utmp.h>
25
26#ifndef SHUTDOWN_TIME
27#define SHUTDOWN_TIME 254
28#endif
29
30GLOBALS(
31  char *file;
32
33  struct arg_list *list;
34)
35
36static void free_list()
37{
38  if (TT.list) {
39    llist_traverse(TT.list, llist_free_arg);
40    TT.list = NULL;
41  }
42}
43
44static void llist_add_node(struct arg_list **old, void *data)
45{
46  struct arg_list *new = xmalloc(sizeof(struct arg_list));
47
48  new->arg = (char*)data;
49  new->next = *old;
50  *old = new;
51}
52
53// Find a node and dlink it from the list.
54static struct arg_list *find_and_dlink(struct arg_list **list, char *devname)
55{
56  struct arg_list *l = *list;
57
58  while (*list) {
59    struct utmp *ut = (struct utmp *)l->arg;
60
61    if (!strncmp(ut->ut_line, devname, UT_LINESIZE)) {
62      *list = (*list)->next;
63      return l;
64    }
65    list = &(*list)->next;
66    l = *list;
67  }
68  return NULL;
69}
70
71// Compute login, logout and duration of login.
72static void seize_duration(time_t tm0, time_t tm1)
73{
74  unsigned days, hours, mins;
75  double diff = difftime(tm1, tm0);
76
77  diff = (diff > 0) ? (tm1 - tm0) : 0;
78  toybuf[0] = toybuf[18] = toybuf[28] = '\0';
79  strncpy(toybuf, ctime(&tm0), 16); // Login Time.
80  snprintf(toybuf+18, 8, "- %s", ctime(&tm1) + 11); // Logout Time.
81  days = (mins = diff/60)/(24*60);
82  hours = (mins = (mins%(24*60)))/60;
83  mins = mins%60;
84  sprintf(toybuf+28, "(%u+%02u:%02u)", days, hours, mins); // Duration.
85}
86
87void last_main(void)
88{
89  struct utmp ut;
90  time_t tm[3] = {0,}; //array for time avlues, previous, current
91  char *file = "/var/log/wtmp";
92  int fd, pwidth, curlog_type = EMPTY;
93  off_t loc;
94
95  if (toys.optflags & FLAG_f) file = TT.file;
96
97  pwidth = (toys.optflags & FLAG_W) ? 46 : 16;
98  *tm = time(tm+1);
99  fd = xopen(file, O_RDONLY);
100  loc = xlseek(fd, 0, SEEK_END);
101
102  // Loop through file structures in reverse order.
103  for (;;) {
104    loc -= sizeof(ut);
105    if(loc < 0) break;
106    xlseek(fd, loc, SEEK_SET);
107
108    // Read next structure, determine type
109    xreadall(fd, &ut, sizeof(ut));
110    *tm = ut.ut_tv.tv_sec;
111    if (*ut.ut_line == '~') {
112      if (!strcmp(ut.ut_user, "runlevel")) ut.ut_type = RUN_LVL;
113      else if (!strcmp(ut.ut_user, "reboot")) ut.ut_type = BOOT_TIME;
114      else if (!strcmp(ut.ut_user, "shutdown")) ut.ut_type = SHUTDOWN_TIME;
115    } else if (!*ut.ut_user) ut.ut_type = DEAD_PROCESS;
116    else if (*ut.ut_user && *ut.ut_line && ut.ut_type != DEAD_PROCESS
117        && strcmp(ut.ut_user, "LOGIN")) ut.ut_type = USER_PROCESS;
118    /* The pair of terminal names '|' / '}' logs the
119     * old/new system time when date changes it.
120     */
121    if (!strcmp(ut.ut_user, "date")) {
122      if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME;
123      if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME;
124    }
125
126    if ((ut.ut_type == SHUTDOWN_TIME) || ((ut.ut_type == RUN_LVL) &&
127        (((ut.ut_pid & 255) == '0') || ((ut.ut_pid & 255) == '6'))))
128    {
129      tm[1] = tm[2] = (time_t)ut.ut_tv.tv_sec;
130      free_list();
131      curlog_type = RUN_LVL;
132    } else if (ut.ut_type == BOOT_TIME) {
133      seize_duration(tm[0], tm[1]);
134      strcpy(ut.ut_line, "system boot");
135      free_list();
136      printf("%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n", ut.ut_user,
137          ut.ut_line, pwidth, pwidth, ut.ut_host,
138          toybuf, toybuf+18, toybuf+28);
139      curlog_type = BOOT_TIME;
140      tm[2] = (time_t)ut.ut_tv.tv_sec;
141    } else if (ut.ut_type == USER_PROCESS && *ut.ut_line) {
142      struct arg_list *l = find_and_dlink(&TT.list, ut.ut_line);
143
144      if (l) {
145        struct utmp *u = (struct utmp *)l->arg;
146        seize_duration(tm[0], u->ut_tv.tv_sec);
147        printf("%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n", ut.ut_user,
148            ut.ut_line, pwidth, pwidth, ut.ut_host,
149            toybuf, toybuf+18, toybuf+28);
150        free(l->arg);
151        free(l);
152      } else {
153        int type = !tm[2] ? EMPTY : curlog_type;
154        if (!tm[2]) { //check process's current status (alive or dead).
155          if ((ut.ut_pid > 0) && (kill(ut.ut_pid, 0)!=0) && (errno == ESRCH))
156            type = INIT_PROCESS;
157        }
158        seize_duration(tm[0], tm[2]);
159        switch (type) {
160          case EMPTY:
161            strcpy(toybuf+18, "  still");
162            strcpy(toybuf+28, "logged in");
163            break;
164          case RUN_LVL:
165            strcpy(toybuf+18, "- down ");
166            break;
167          case BOOT_TIME:
168            strcpy(toybuf+18, "- crash");
169            break;
170          case INIT_PROCESS:
171            strcpy(toybuf+18, "   gone");
172            strcpy(toybuf+28, "- no logout");
173            break;
174          default:
175            break;
176        }
177        printf("%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n", ut.ut_user,
178            ut.ut_line, pwidth, pwidth, ut.ut_host,
179            toybuf, toybuf+18, toybuf+28);
180      }
181      llist_add_node(&TT.list, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
182    } else if (ut.ut_type == DEAD_PROCESS && *ut.ut_line)
183      llist_add_node(&TT.list, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
184
185    loc -= sizeof(ut);
186    if(loc < 0) break;
187    xlseek(fd, loc, SEEK_SET);
188  }
189
190  if (CFG_TOYBOX_FREE) {
191    xclose(fd);
192    free_list();
193  }
194
195  xprintf("\n%s begins %-24.24s\n", basename(file), ctime(tm));
196}
197