blob: 2b7fee6e58430e4e76aba2c6a357d62937f160c6 [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
Denis Vlasenkocd2663f2008-06-01 22:36:39 +000013/* NB: ut_name and ut_user are the same field, use only one name (ut_user)
14 * to reduce confusion */
15
Bernhard Reutner-Fischer69d5ba22008-05-22 21:56:26 +000016#ifndef SHUTDOWN_TIME
17# define SHUTDOWN_TIME 254
18#endif
19
Denis Vlasenko7b386392008-05-22 17:14:09 +000020#define HEADER_FORMAT "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n"
Denis Vlasenko869d3d32008-05-22 02:07:58 +000021#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
26enum {
27 NORMAL,
28 LOGGED,
29 DOWN,
30 REBOOT,
31 CRASH,
32 GONE
33};
34
35enum {
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
43static 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
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +000052 safe_strncpy(login_time, ctime(&(ut->ut_tv.tv_sec)), 17);
Denis Vlasenko869d3d32008-05-22 02:07:58 +000053 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
Denis Vlasenko30f892a2008-05-25 01:14:14 +000063// if (days) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +000064 sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
Denis Vlasenko30f892a2008-05-25 01:14:14 +000065// } else {
66// sprintf(duration, " (%02u:%02u)", hours, mins);
67// }
Denis Vlasenko869d3d32008-05-22 02:07:58 +000068
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,
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +000093 ut->ut_user,
Denis Vlasenko869d3d32008-05-22 02:07:58 +000094 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
103static int get_ut_type(struct utmp *ut)
104{
105 if (ut->ut_line[0] == '~') {
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000106 if (strcmp(ut->ut_user, "shutdown") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000107 return SHUTDOWN_TIME;
108 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000109 if (strcmp(ut->ut_user, "reboot") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000110 return BOOT_TIME;
111 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000112 if (strcmp(ut->ut_user, "runlevel") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000113 return RUN_LVL;
114 }
115 return ut->ut_type;
116 }
117
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000118 if (ut->ut_user[0] == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000119 return DEAD_PROCESS;
120 }
121
122 if ((ut->ut_type != DEAD_PROCESS)
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000123 && (strcmp(ut->ut_user, "LOGIN") != 0)
124 && ut->ut_user[0]
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000125 && ut->ut_line[0]
126 ) {
127 ut->ut_type = USER_PROCESS;
128 }
129
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000130 if (strcmp(ut->ut_user, "date") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000131 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
141static 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
150int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
151int 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);
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000178 {
179 /* in case the file is empty... */
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000180 struct stat st;
181 fstat(file, &st);
182 start_time = st.st_ctime;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000183 }
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000184
185 time(&down_time);
186 going_down = 0;
187 boot_down = NORMAL; /* 0 */
188 zlist = NULL;
189 boot_time = 0;
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000190 /* get file size, rounding down to last full record */
191 pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000192 for (;;) {
193 pos -= (off_t)sizeof(ut);
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000194 if (pos < 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000195 /* Beyond the beginning of the file boundary =>
196 * the whole file has been read. */
197 break;
198 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000199 xlseek(file, pos, SEEK_SET);
200 xread(file, &ut, sizeof(ut));
201 /* rewritten by each record, eventially will have
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000202 * first record's ut_tv.tv_sec: */
203 start_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000204
205 switch (get_ut_type(&ut)) {
206 case SHUTDOWN_TIME:
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000207 down_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000208 boot_down = DOWN;
209 going_down = 1;
210 break;
211 case RUN_LVL:
212 if (is_runlevel_shutdown(&ut)) {
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000213 down_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000214 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) {
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000246 show_entry(&ut, NORMAL, up->ut_tv.tv_sec);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000247 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) {
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000277 boot_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000278 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
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000288 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}