blob: 8194e31b5675e51d0db57e7601fbabb03745086a [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
Bernhard Reutner-Fischer86a7f182015-04-02 23:03:46 +020025#if !defined __UT_LINESIZE && defined UT_LINESIZE
26# define __UT_LINESIZE UT_LINESIZE
27#endif
28
Denis Vlasenko869d3d32008-05-22 02:07:58 +000029enum {
30 NORMAL,
31 LOGGED,
32 DOWN,
33 REBOOT,
34 CRASH,
35 GONE
36};
37
38enum {
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
Bernhard Reutner-Fischer86a7f182015-04-02 23:03:46 +020046static void show_entry(struct utmpx *ut, int state, time_t dur_secs)
Denis Vlasenko869d3d32008-05-22 02:07:58 +000047{
48 unsigned days, hours, mins;
Denys Vlasenko54e95852015-02-18 13:47:27 +010049 char duration[sizeof("(%u+02:02)") + sizeof(int)*3];
Denis Vlasenko869d3d32008-05-22 02:07:58 +000050 char login_time[17];
51 char logout_time[8];
52 const char *logout_str;
53 const char *duration_str;
Denis Vlasenkod38d38e2008-07-09 19:48:43 +000054 time_t tmp;
Denis Vlasenko869d3d32008-05-22 02:07:58 +000055
Denis Vlasenkod38d38e2008-07-09 19:48:43 +000056 /* manpages say ut_tv.tv_sec *is* time_t,
57 * but some systems have it wrong */
Denis Vlasenko4131d852008-07-09 22:04:37 +000058 tmp = ut->ut_tv.tv_sec;
Denis Vlasenkod38d38e2008-07-09 19:48:43 +000059 safe_strncpy(login_time, ctime(&tmp), 17);
Denys Vlasenko54e95852015-02-18 13:47:27 +010060 tmp = dur_secs;
61 snprintf(logout_time, 8, "- %s", ctime(&tmp) + 11);
Denis Vlasenko869d3d32008-05-22 02:07:58 +000062
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
Denis Vlasenko30f892a2008-05-25 01:14:14 +000071// if (days) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +000072 sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
Denis Vlasenko30f892a2008-05-25 01:14:14 +000073// } else {
74// sprintf(duration, " (%02u:%02u)", hours, mins);
75// }
Denis Vlasenko869d3d32008-05-22 02:07:58 +000076
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,
Denys Vlasenko60cb48c2013-01-14 15:57:44 +0100101 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);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000109}
110
Bernhard Reutner-Fischer86a7f182015-04-02 23:03:46 +0200111static int get_ut_type(struct utmpx *ut)
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000112{
113 if (ut->ut_line[0] == '~') {
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000114 if (strcmp(ut->ut_user, "shutdown") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000115 return SHUTDOWN_TIME;
116 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000117 if (strcmp(ut->ut_user, "reboot") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000118 return BOOT_TIME;
119 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000120 if (strcmp(ut->ut_user, "runlevel") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000121 return RUN_LVL;
122 }
123 return ut->ut_type;
124 }
125
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000126 if (ut->ut_user[0] == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000127 return DEAD_PROCESS;
128 }
129
130 if ((ut->ut_type != DEAD_PROCESS)
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000131 && (strcmp(ut->ut_user, "LOGIN") != 0)
132 && ut->ut_user[0]
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000133 && ut->ut_line[0]
134 ) {
135 ut->ut_type = USER_PROCESS;
136 }
137
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000138 if (strcmp(ut->ut_user, "date") == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000139 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
Bernhard Reutner-Fischer86a7f182015-04-02 23:03:46 +0200149static int is_runlevel_shutdown(struct utmpx *ut)
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000150{
151 if (((ut->ut_pid & 255) == '0') || ((ut->ut_pid & 255) == '6')) {
152 return 1;
153 }
154
155 return 0;
156}
157
158int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000159int last_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000160{
Bernhard Reutner-Fischer86a7f182015-04-02 23:03:46 +0200161 struct utmpx ut;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000162 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;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000169 smallint going_down;
170 smallint boot_down;
171
Denys Vlasenko60a94142011-05-13 20:57:01 +0200172 /*opt =*/ getopt32(argv, "Wf:" /* "H" */, &filename);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000173#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);
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000185 {
186 /* in case the file is empty... */
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000187 struct stat st;
188 fstat(file, &st);
189 start_time = st.st_ctime;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000190 }
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000191
192 time(&down_time);
193 going_down = 0;
194 boot_down = NORMAL; /* 0 */
195 zlist = NULL;
196 boot_time = 0;
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000197 /* get file size, rounding down to last full record */
198 pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000199 for (;;) {
200 pos -= (off_t)sizeof(ut);
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000201 if (pos < 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000202 /* Beyond the beginning of the file boundary =>
203 * the whole file has been read. */
204 break;
205 }
Denis Vlasenko30f892a2008-05-25 01:14:14 +0000206 xlseek(file, pos, SEEK_SET);
207 xread(file, &ut, sizeof(ut));
208 /* rewritten by each record, eventially will have
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000209 * first record's ut_tv.tv_sec: */
210 start_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000211
212 switch (get_ut_type(&ut)) {
213 case SHUTDOWN_TIME:
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000214 down_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000215 boot_down = DOWN;
216 going_down = 1;
217 break;
218 case RUN_LVL:
219 if (is_runlevel_shutdown(&ut)) {
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000220 down_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000221 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, memcpy(xmalloc(sizeof(ut)), &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) {
Bernhard Reutner-Fischer86a7f182015-04-02 23:03:46 +0200249 struct utmpx *up = (struct utmpx *)el->data;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000250 next = el->link;
Bernhard Reutner-Fischer86a7f182015-04-02 23:03:46 +0200251 if (strncmp(up->ut_line, ut.ut_line, __UT_LINESIZE) == 0) {
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000252 if (show) {
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000253 show_entry(&ut, NORMAL, up->ut_tv.tv_sec);
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000254 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, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
279 break;
280 }
281 }
282
283 if (going_down) {
Bernhard Reutner-Fischer62d85032008-06-01 10:10:22 +0000284 boot_time = ut.ut_tv.tv_sec;
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000285 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
Denis Vlasenko869d3d32008-05-22 02:07:58 +0000295 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}