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 
show_entry(struct utmpx * ut,int state,time_t dur_secs)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 
get_ut_type(struct utmpx * ut)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 
is_runlevel_shutdown(struct utmpx * ut)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;
last_main(int argc UNUSED_PARAM,char ** argv)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 }
301