diff options
Diffstat (limited to 'util-linux/last_fancy.c')
-rw-r--r-- | util-linux/last_fancy.c | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/util-linux/last_fancy.c b/util-linux/last_fancy.c new file mode 100644 index 000000000..e56e0ba85 --- /dev/null +++ b/util-linux/last_fancy.c | |||
@@ -0,0 +1,300 @@ | |||
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 | } | ||