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