blob: 16ed9e9205983775062254619e405d630e89785a [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 *
Denys Vlasenko0ef64bd2010-08-16 20:14:46 +02007 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
Denis Vlasenko869d3d32008-05-22 02:07:58 +00008 */
9
Bernhard Reutner-Fischer69d5ba22008-05-22 21:56:26 +000010#include "libbb.h"
Bernhard Reutner-Fischer69d5ba22008-05-22 21:56:26 +000011
Denis Vlasenkocd2663f2008-06-01 22:36:39 +000012/* NB: ut_name and ut_user are the same field, use only one name (ut_user)
13 * to reduce confusion */
14
Bernhard Reutner-Fischer69d5ba22008-05-22 21:56:26 +000015#ifndef SHUTDOWN_TIME
16# define SHUTDOWN_TIME 254
17#endif
18
Denis Vlasenko7b386392008-05-22 17:14:09 +000019#define HEADER_FORMAT "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n"
Denis Vlasenko869d3d32008-05-22 02:07:58 +000020#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
25enum {
26 NORMAL,
27 LOGGED,
28 DOWN,
29 REBOOT,
30 CRASH,
31 GONE
32};
33
34enum {
35 LAST_OPT_W = (1 << 0), /* -W wide */
36 LAST_OPT_f = (1 << 1), /* -f input file */
37 LAST_OPT_H = (1 << 2), /* -H header */
38};
39
40#define show_wide (option_mask32 & LAST_OPT_W)
41
42static void show_entry(struct utmp *ut, int state, time_t dur_secs)
43{
44 unsigned days, hours, mins;
Denys Vlasenko54e95852015-02-18 13:47:27 +010045 char duration[sizeof("(%u+02:02)") + sizeof(int)*3];
Denis Vlasenko869d3d32008-05-22 02:07:58 +000046 char login_time[17];
47 char logout_time[8];
48 const char *logout_str;
49 const char *duration_str;
Denis Vlasenkod38d38e2008-07-09 19:48:43 +000050 time_t tmp;
Denis Vlasenko869d3d32008-05-22 02:07:58 +000051
Denis Vlasenkod38d38e2008-07-09 19:48:43 +000052 /* manpages say ut_tv.tv_sec *is* time_t,
53 * but some systems have it wrong */
Denis Vlasenko4131d852008-07-09 22:04:37 +000054 tmp = ut->ut_tv.tv_sec;
Denis Vlasenkod38d38e2008-07-09 19:48:43 +000055 safe_strncpy(login_time, ctime(&tmp), 17);
Denys Vlasenko54e95852015-02-18 13:47:27 +010056 tmp = dur_secs;
57 snprintf(logout_time, 8, "- %s", ctime(&tmp) + 11);
Denis Vlasenko869d3d32008-05-22 02:07:58 +000058
59 dur_secs = MAX(dur_secs - (time_t)ut->ut_tv.tv_sec, (time_t)0);
60 /* unsigned int is easier to divide than time_t (which may be signed long) */
61 mins = dur_secs / 60;
62 days = mins / (24*60);
63 mins = mins % (24*60);
64 hours = mins / 60;
65 mins = mins % 60;
66
Denis Vlasenko30f892a2008-05-25 01:14:14 +000067// if (days) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +000068 sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
Denis Vlasenko30f892a2008-05-25 01:14:14 +000069// } else {
70// sprintf(duration, " (%02u:%02u)", hours, mins);
71// }
Denis Vlasenko869d3d32008-05-22 02:07:58 +000072
73 logout_str = logout_time;
74 duration_str = duration;
75 switch (state) {
76 case NORMAL:
77 break;
78 case LOGGED:
79 logout_str = " still";
80 duration_str = "logged in";
81 break;
82 case DOWN:
83 logout_str = "- down ";
84 break;
85 case REBOOT:
86 break;
87 case CRASH:
88 logout_str = "- crash";
89 break;
90 case GONE:
91 logout_str = " gone";
92 duration_str = "- no logout";
93 break;
94 }
95
96 printf(HEADER_FORMAT,
Denys Vlasenko60cb48c2013-01-14 15:57:44 +010097 ut->ut_user,
98 ut->ut_line,
99 show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
100 show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
101 ut->ut_host,
102 login_time,
103 logout_str,
104 duration_str);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000105}
106
107static int get_ut_type(struct utmp *ut)
108{
109 if (ut->ut_line[0] == '~') {
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000110 if (strcmp(ut->ut_user, "shutdown") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000111 return SHUTDOWN_TIME;
112 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000113 if (strcmp(ut->ut_user, "reboot") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000114 return BOOT_TIME;
115 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000116 if (strcmp(ut->ut_user, "runlevel") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000117 return RUN_LVL;
118 }
119 return ut->ut_type;
120 }
121
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000122 if (ut->ut_user[0] == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000123 return DEAD_PROCESS;
124 }
125
126 if ((ut->ut_type != DEAD_PROCESS)
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000127 && (strcmp(ut->ut_user, "LOGIN") != 0)
128 && ut->ut_user[0]
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000129 && ut->ut_line[0]
130 ) {
131 ut->ut_type = USER_PROCESS;
132 }
133
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000134 if (strcmp(ut->ut_user, "date") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000135 if (ut->ut_line[0] == '|') {
136 return OLD_TIME;
137 }
138 if (ut->ut_line[0] == '{') {
139 return NEW_TIME;
140 }
141 }
142 return ut->ut_type;
143}
144
145static int is_runlevel_shutdown(struct utmp *ut)
146{
147 if (((ut->ut_pid & 255) == '0') || ((ut->ut_pid & 255) == '6')) {
148 return 1;
149 }
150
151 return 0;
152}
153
154int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000155int last_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000156{
157 struct utmp ut;
158 const char *filename = _PATH_WTMP;
159 llist_t *zlist;
160 off_t pos;
161 time_t start_time;
162 time_t boot_time;
163 time_t down_time;
164 int file;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000165 smallint going_down;
166 smallint boot_down;
167
Denys Vlasenko60a94142011-05-13 20:57:01 +0200168 /*opt =*/ getopt32(argv, "Wf:" /* "H" */, &filename);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000169#ifdef BUT_UTIL_LINUX_LAST_HAS_NO_SUCH_OPT
170 if (opt & LAST_OPT_H) {
171 /* Print header line */
172 if (opt & LAST_OPT_W) {
173 printf(HEADER_FORMAT, HEADER_LINE_WIDE);
174 } else {
175 printf(HEADER_FORMAT, HEADER_LINE);
176 }
177 }
178#endif
179
180 file = xopen(filename, O_RDONLY);
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000181 {
182 /* in case the file is empty... */
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000183 struct stat st;
184 fstat(file, &st);
185 start_time = st.st_ctime;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000186 }
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000187
188 time(&down_time);
189 going_down = 0;
190 boot_down = NORMAL; /* 0 */
191 zlist = NULL;
192 boot_time = 0;
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000193 /* get file size, rounding down to last full record */
194 pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000195 for (;;) {
196 pos -= (off_t)sizeof(ut);
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000197 if (pos < 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000198 /* Beyond the beginning of the file boundary =>
199 * the whole file has been read. */
200 break;
201 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000202 xlseek(file, pos, SEEK_SET);
203 xread(file, &ut, sizeof(ut));
204 /* rewritten by each record, eventially will have
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000205 * first record's ut_tv.tv_sec: */
206 start_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000207
208 switch (get_ut_type(&ut)) {
209 case SHUTDOWN_TIME:
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000210 down_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000211 boot_down = DOWN;
212 going_down = 1;
213 break;
214 case RUN_LVL:
215 if (is_runlevel_shutdown(&ut)) {
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000216 down_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000217 going_down = 1;
218 boot_down = DOWN;
219 }
220 break;
221 case BOOT_TIME:
222 strcpy(ut.ut_line, "system boot");
223 show_entry(&ut, REBOOT, down_time);
224 boot_down = CRASH;
225 going_down = 1;
226 break;
227 case DEAD_PROCESS:
228 if (!ut.ut_line[0]) {
229 break;
230 }
231 /* add_entry */
232 llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
233 break;
234 case USER_PROCESS: {
235 int show;
236
237 if (!ut.ut_line[0]) {
238 break;
239 }
240 /* find_entry */
241 show = 1;
242 {
243 llist_t *el, *next;
244 for (el = zlist; el; el = next) {
245 struct utmp *up = (struct utmp *)el->data;
246 next = el->link;
247 if (strncmp(up->ut_line, ut.ut_line, UT_LINESIZE) == 0) {
248 if (show) {
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000249 show_entry(&ut, NORMAL, up->ut_tv.tv_sec);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000250 show = 0;
251 }
252 llist_unlink(&zlist, el);
253 free(el->data);
254 free(el);
255 }
256 }
257 }
258
259 if (show) {
260 int state = boot_down;
261
262 if (boot_time == 0) {
263 state = LOGGED;
264 /* Check if the process is alive */
265 if ((ut.ut_pid > 0)
266 && (kill(ut.ut_pid, 0) != 0)
267 && (errno == ESRCH)) {
268 state = GONE;
269 }
270 }
271 show_entry(&ut, state, boot_time);
272 }
273 /* add_entry */
274 llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
275 break;
276 }
277 }
278
279 if (going_down) {
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000280 boot_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000281 llist_free(zlist, free);
282 zlist = NULL;
283 going_down = 0;
284 }
285 }
286
287 if (ENABLE_FEATURE_CLEAN_UP) {
288 llist_free(zlist, free);
289 }
290
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000291 printf("\nwtmp begins %s", ctime(&start_time));
292
293 if (ENABLE_FEATURE_CLEAN_UP)
294 close(file);
295 fflush_stdout_and_exit(EXIT_SUCCESS);
296}