blob: d234a48bfc6ff583a4c6ba9d7eea93b969861c46 [file] [log] [blame]
Denis Vlasenko869d3d32008-05-22 02:07:58 +00001/* 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
Bernhard Reutner-Fischer69d5ba22008-05-22 21:56:26 +000010#include "libbb.h"
11#include <utmp.h>
12
13#ifndef SHUTDOWN_TIME
14# define SHUTDOWN_TIME 254
15#endif
16
Denis Vlasenko7b386392008-05-22 17:14:09 +000017#define HEADER_FORMAT "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n"
Denis Vlasenko869d3d32008-05-22 02:07:58 +000018#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
23enum {
24 NORMAL,
25 LOGGED,
26 DOWN,
27 REBOOT,
28 CRASH,
29 GONE
30};
31
32enum {
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
40static 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_time)), 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
Denis Vlasenko30f892a2008-05-25 01:14:14 +000060// if (days) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +000061 sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
Denis Vlasenko30f892a2008-05-25 01:14:14 +000062// } else {
63// sprintf(duration, " (%02u:%02u)", hours, mins);
64// }
Denis Vlasenko869d3d32008-05-22 02:07:58 +000065
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_name,
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
100static int get_ut_type(struct utmp *ut)
101{
102 if (ut->ut_line[0] == '~') {
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000103 if (strcmp(ut->ut_user, "shutdown") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000104 return SHUTDOWN_TIME;
105 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000106 if (strcmp(ut->ut_user, "reboot") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000107 return BOOT_TIME;
108 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000109 if (strcmp(ut->ut_user, "runlevel") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000110 return RUN_LVL;
111 }
112 return ut->ut_type;
113 }
114
115 if (ut->ut_name[0] == 0) {
116 return DEAD_PROCESS;
117 }
118
119 if ((ut->ut_type != DEAD_PROCESS)
120 && (strcmp(ut->ut_name, "LOGIN") != 0)
121 && ut->ut_name[0]
122 && ut->ut_line[0]
123 ) {
124 ut->ut_type = USER_PROCESS;
125 }
126
127 if (strcmp(ut->ut_name, "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
138static 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
147int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
148int 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);
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000175 {
176 /* in case the file is empty... */
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000177 struct stat st;
178 fstat(file, &st);
179 start_time = st.st_ctime;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000180 }
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000181
182 time(&down_time);
183 going_down = 0;
184 boot_down = NORMAL; /* 0 */
185 zlist = NULL;
186 boot_time = 0;
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000187 /* get file size, rounding down to last full record */
188 pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000189 for (;;) {
190 pos -= (off_t)sizeof(ut);
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000191 if (pos < 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000192 /* Beyond the beginning of the file boundary =>
193 * the whole file has been read. */
194 break;
195 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000196 xlseek(file, pos, SEEK_SET);
197 xread(file, &ut, sizeof(ut));
198 /* rewritten by each record, eventially will have
199 * first record's ut_time: */
200 start_time = ut.ut_time;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000201
202 switch (get_ut_type(&ut)) {
203 case SHUTDOWN_TIME:
204 down_time = ut.ut_time;
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_time;
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_time);
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_time;
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
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000285 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}