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