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