294 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* vi: set sw=4 ts=4: */
 | 
						|
/*
 | 
						|
 * (sysvinit like) last implementation
 | 
						|
 *
 | 
						|
 * Copyright (C) 2008 by Patricia Muscalu <patricia.muscalu@axis.com>
 | 
						|
 *
 | 
						|
 * Licensed under the GPLv2 or later, see the file LICENSE in this tarball.
 | 
						|
 */
 | 
						|
 | 
						|
#include "libbb.h"
 | 
						|
#include <utmp.h>
 | 
						|
 | 
						|
/* NB: ut_name and ut_user are the same field, use only one name (ut_user)
 | 
						|
 * to reduce confusion */
 | 
						|
 | 
						|
#ifndef SHUTDOWN_TIME
 | 
						|
#  define SHUTDOWN_TIME 254
 | 
						|
#endif
 | 
						|
 | 
						|
#define HEADER_FORMAT     "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n"
 | 
						|
#define HEADER_LINE       "USER", "TTY", \
 | 
						|
	INET_ADDRSTRLEN, INET_ADDRSTRLEN, "HOST", "LOGIN", "  TIME", ""
 | 
						|
#define HEADER_LINE_WIDE  "USER", "TTY", \
 | 
						|
	INET6_ADDRSTRLEN, INET6_ADDRSTRLEN, "HOST", "LOGIN", "  TIME", ""
 | 
						|
 | 
						|
enum {
 | 
						|
	NORMAL,
 | 
						|
	LOGGED,
 | 
						|
	DOWN,
 | 
						|
	REBOOT,
 | 
						|
	CRASH,
 | 
						|
	GONE
 | 
						|
};
 | 
						|
 | 
						|
enum {
 | 
						|
	LAST_OPT_W = (1 << 0),  /* -W wide            */
 | 
						|
	LAST_OPT_f = (1 << 1),  /* -f input file      */
 | 
						|
	LAST_OPT_H = (1 << 2),  /* -H header          */
 | 
						|
};
 | 
						|
 | 
						|
#define show_wide (option_mask32 & LAST_OPT_W)
 | 
						|
 | 
						|
static void show_entry(struct utmp *ut, int state, time_t dur_secs)
 | 
						|
{
 | 
						|
	unsigned days, hours, mins;
 | 
						|
	char duration[32];
 | 
						|
	char login_time[17];
 | 
						|
	char logout_time[8];
 | 
						|
	const char *logout_str;
 | 
						|
	const char *duration_str;
 | 
						|
 | 
						|
	safe_strncpy(login_time, ctime(&(ut->ut_tv.tv_sec)), 17);
 | 
						|
	snprintf(logout_time, 8, "- %s", ctime(&dur_secs) + 11);
 | 
						|
 | 
						|
	dur_secs = MAX(dur_secs - (time_t)ut->ut_tv.tv_sec, (time_t)0);
 | 
						|
	/* unsigned int is easier to divide than time_t (which may be signed long) */
 | 
						|
	mins = dur_secs / 60;
 | 
						|
	days = mins / (24*60);
 | 
						|
	mins = mins % (24*60);
 | 
						|
	hours = mins / 60;
 | 
						|
	mins = mins % 60;
 | 
						|
 | 
						|
//	if (days) {
 | 
						|
		sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
 | 
						|
//	} else {
 | 
						|
//		sprintf(duration, " (%02u:%02u)", hours, mins);
 | 
						|
//	}
 | 
						|
 | 
						|
	logout_str = logout_time;
 | 
						|
	duration_str = duration;
 | 
						|
	switch (state) {
 | 
						|
	case NORMAL:
 | 
						|
		break;
 | 
						|
	case LOGGED:
 | 
						|
		logout_str = "  still";
 | 
						|
		duration_str = "logged in";
 | 
						|
		break;
 | 
						|
	case DOWN:
 | 
						|
		logout_str = "- down ";
 | 
						|
		break;
 | 
						|
	case REBOOT:
 | 
						|
		break;
 | 
						|
	case CRASH:
 | 
						|
		logout_str = "- crash";
 | 
						|
		break;
 | 
						|
	case GONE:
 | 
						|
		logout_str = "   gone";
 | 
						|
		duration_str = "- no logout";
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	printf(HEADER_FORMAT,
 | 
						|
		   ut->ut_user,
 | 
						|
		   ut->ut_line,
 | 
						|
		   show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
 | 
						|
		   show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
 | 
						|
		   ut->ut_host,
 | 
						|
		   login_time,
 | 
						|
		   logout_str,
 | 
						|
		   duration_str);
 | 
						|
}
 | 
						|
 | 
						|
static int get_ut_type(struct utmp *ut)
 | 
						|
{
 | 
						|
	if (ut->ut_line[0] == '~') {
 | 
						|
		if (strcmp(ut->ut_user, "shutdown") == 0) {
 | 
						|
			return SHUTDOWN_TIME;
 | 
						|
		}
 | 
						|
		if (strcmp(ut->ut_user, "reboot") == 0) {
 | 
						|
			return BOOT_TIME;
 | 
						|
		}
 | 
						|
		if (strcmp(ut->ut_user, "runlevel") == 0) {
 | 
						|
			return RUN_LVL;
 | 
						|
		}
 | 
						|
		return ut->ut_type;
 | 
						|
	}
 | 
						|
 | 
						|
	if (ut->ut_user[0] == 0) {
 | 
						|
		return DEAD_PROCESS;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((ut->ut_type != DEAD_PROCESS)
 | 
						|
	 && (strcmp(ut->ut_user, "LOGIN") != 0)
 | 
						|
	 && ut->ut_user[0]
 | 
						|
	 && ut->ut_line[0]
 | 
						|
	) {
 | 
						|
		ut->ut_type = USER_PROCESS;
 | 
						|
	}
 | 
						|
 | 
						|
	if (strcmp(ut->ut_user, "date") == 0) {
 | 
						|
		if (ut->ut_line[0] == '|') {
 | 
						|
			return OLD_TIME;
 | 
						|
		}
 | 
						|
		if (ut->ut_line[0] == '{') {
 | 
						|
			return NEW_TIME;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ut->ut_type;
 | 
						|
}
 | 
						|
 | 
						|
static int is_runlevel_shutdown(struct utmp *ut)
 | 
						|
{
 | 
						|
	if (((ut->ut_pid & 255) == '0') || ((ut->ut_pid & 255) == '6')) {
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 | 
						|
int last_main(int argc ATTRIBUTE_UNUSED, char **argv)
 | 
						|
{
 | 
						|
	struct utmp ut;
 | 
						|
	const char *filename = _PATH_WTMP;
 | 
						|
	llist_t *zlist;
 | 
						|
	off_t pos;
 | 
						|
	time_t start_time;
 | 
						|
	time_t boot_time;
 | 
						|
	time_t down_time;
 | 
						|
	int file;
 | 
						|
	unsigned opt;
 | 
						|
	smallint going_down;
 | 
						|
	smallint boot_down;
 | 
						|
 | 
						|
	opt = getopt32(argv, "Wf:" /* "H" */, &filename);
 | 
						|
#ifdef BUT_UTIL_LINUX_LAST_HAS_NO_SUCH_OPT
 | 
						|
	if (opt & LAST_OPT_H) {
 | 
						|
		/* Print header line */
 | 
						|
		if (opt & LAST_OPT_W) {
 | 
						|
			printf(HEADER_FORMAT, HEADER_LINE_WIDE);
 | 
						|
		} else {
 | 
						|
			printf(HEADER_FORMAT, HEADER_LINE);
 | 
						|
		}
 | 
						|
	}
 | 
						|
#endif
 | 
						|
 | 
						|
	file = xopen(filename, O_RDONLY);
 | 
						|
	{
 | 
						|
		/* in case the file is empty... */
 | 
						|
		struct stat st;
 | 
						|
		fstat(file, &st);
 | 
						|
		start_time = st.st_ctime;
 | 
						|
	}
 | 
						|
 | 
						|
	time(&down_time);
 | 
						|
	going_down = 0;
 | 
						|
	boot_down = NORMAL; /* 0 */
 | 
						|
	zlist = NULL;
 | 
						|
	boot_time = 0;
 | 
						|
	/* get file size, rounding down to last full record */
 | 
						|
	pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut);
 | 
						|
	for (;;) {
 | 
						|
		pos -= (off_t)sizeof(ut);
 | 
						|
		if (pos < 0) {
 | 
						|
			/* Beyond the beginning of the file boundary =>
 | 
						|
			 * the whole file has been read. */
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		xlseek(file, pos, SEEK_SET);
 | 
						|
		xread(file, &ut, sizeof(ut));
 | 
						|
		/* rewritten by each record, eventially will have
 | 
						|
		 * first record's ut_tv.tv_sec: */
 | 
						|
		start_time = ut.ut_tv.tv_sec;
 | 
						|
 | 
						|
		switch (get_ut_type(&ut)) {
 | 
						|
		case SHUTDOWN_TIME:
 | 
						|
			down_time = ut.ut_tv.tv_sec;
 | 
						|
			boot_down = DOWN;
 | 
						|
			going_down = 1;
 | 
						|
			break;
 | 
						|
		case RUN_LVL:
 | 
						|
			if (is_runlevel_shutdown(&ut)) {
 | 
						|
				down_time = ut.ut_tv.tv_sec;
 | 
						|
				going_down = 1;
 | 
						|
				boot_down = DOWN;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		case BOOT_TIME:
 | 
						|
			strcpy(ut.ut_line, "system boot");
 | 
						|
			show_entry(&ut, REBOOT, down_time);
 | 
						|
			boot_down = CRASH;
 | 
						|
			going_down = 1;
 | 
						|
			break;
 | 
						|
		case DEAD_PROCESS:
 | 
						|
			if (!ut.ut_line[0]) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			/* add_entry */
 | 
						|
			llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
 | 
						|
			break;
 | 
						|
		case USER_PROCESS: {
 | 
						|
			int show;
 | 
						|
 | 
						|
			if (!ut.ut_line[0]) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			/* find_entry */
 | 
						|
			show = 1;
 | 
						|
			{
 | 
						|
				llist_t *el, *next;
 | 
						|
				for (el = zlist; el; el = next) {
 | 
						|
					struct utmp *up = (struct utmp *)el->data;
 | 
						|
					next = el->link;
 | 
						|
					if (strncmp(up->ut_line, ut.ut_line, UT_LINESIZE) == 0) {
 | 
						|
						if (show) {
 | 
						|
							show_entry(&ut, NORMAL, up->ut_tv.tv_sec);
 | 
						|
							show = 0;
 | 
						|
						}
 | 
						|
						llist_unlink(&zlist, el);
 | 
						|
						free(el->data);
 | 
						|
						free(el);
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if (show) {
 | 
						|
				int state = boot_down;
 | 
						|
 | 
						|
				if (boot_time == 0) {
 | 
						|
					state = LOGGED;
 | 
						|
					/* Check if the process is alive */
 | 
						|
					if ((ut.ut_pid > 0)
 | 
						|
					 && (kill(ut.ut_pid, 0) != 0)
 | 
						|
					 && (errno == ESRCH)) {
 | 
						|
						state = GONE;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				show_entry(&ut, state, boot_time);
 | 
						|
			}
 | 
						|
			/* add_entry */
 | 
						|
			llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		}
 | 
						|
 | 
						|
		if (going_down) {
 | 
						|
			boot_time = ut.ut_tv.tv_sec;
 | 
						|
			llist_free(zlist, free);
 | 
						|
			zlist = NULL;
 | 
						|
			going_down = 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (ENABLE_FEATURE_CLEAN_UP) {
 | 
						|
		llist_free(zlist, free);
 | 
						|
	}
 | 
						|
 | 
						|
	printf("\nwtmp begins %s", ctime(&start_time));
 | 
						|
 | 
						|
	if (ENABLE_FEATURE_CLEAN_UP)
 | 
						|
		close(file);
 | 
						|
	fflush_stdout_and_exit(EXIT_SUCCESS);
 | 
						|
}
 |