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