last.c revision 60938
11590Srgrimes/*
21590Srgrimes * Copyright (c) 1987, 1993, 1994
31590Srgrimes *	The Regents of the University of California.  All rights reserved.
41590Srgrimes *
51590Srgrimes * Redistribution and use in source and binary forms, with or without
61590Srgrimes * modification, are permitted provided that the following conditions
71590Srgrimes * are met:
81590Srgrimes * 1. Redistributions of source code must retain the above copyright
91590Srgrimes *    notice, this list of conditions and the following disclaimer.
101590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111590Srgrimes *    notice, this list of conditions and the following disclaimer in the
121590Srgrimes *    documentation and/or other materials provided with the distribution.
131590Srgrimes * 3. All advertising materials mentioning features or use of this software
141590Srgrimes *    must display the following acknowledgement:
151590Srgrimes *	This product includes software developed by the University of
161590Srgrimes *	California, Berkeley and its contributors.
171590Srgrimes * 4. Neither the name of the University nor the names of its contributors
181590Srgrimes *    may be used to endorse or promote products derived from this software
191590Srgrimes *    without specific prior written permission.
201590Srgrimes *
211590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311590Srgrimes * SUCH DAMAGE.
3260833Sjake *
3360833Sjake * $FreeBSD: head/usr.bin/last/last.c 60938 2000-05-26 02:09:24Z jake $
341590Srgrimes */
351590Srgrimes
361590Srgrimes#ifndef lint
371590Srgrimesstatic char copyright[] =
381590Srgrimes"@(#) Copyright (c) 1987, 1993, 1994\n\
391590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
401590Srgrimes#endif /* not lint */
411590Srgrimes
421590Srgrimes#ifndef lint
431590Srgrimesstatic char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
441590Srgrimes#endif /* not lint */
451590Srgrimes
461590Srgrimes#include <sys/param.h>
471590Srgrimes#include <sys/stat.h>
481590Srgrimes
491590Srgrimes#include <err.h>
501590Srgrimes#include <fcntl.h>
5116438Sache#include <locale.h>
521590Srgrimes#include <paths.h>
531590Srgrimes#include <signal.h>
541590Srgrimes#include <stdio.h>
551590Srgrimes#include <stdlib.h>
561590Srgrimes#include <string.h>
571590Srgrimes#include <time.h>
581590Srgrimes#include <unistd.h>
591590Srgrimes#include <utmp.h>
6011547Sdg#include <sys/queue.h>
611590Srgrimes
621590Srgrimes#define	NO	0				/* false/no */
631590Srgrimes#define	YES	1				/* true/yes */
641590Srgrimes
651590Srgrimesstatic struct utmp	buf[1024];		/* utmp read buffer */
661590Srgrimes
671590Srgrimestypedef struct arg {
681590Srgrimes	char	*name;				/* argument */
691590Srgrimes#define	HOST_TYPE	-2
701590Srgrimes#define	TTY_TYPE	-3
711590Srgrimes#define	USER_TYPE	-4
721590Srgrimes	int	type;				/* type of arg */
731590Srgrimes	struct arg	*next;			/* linked list pointer */
741590Srgrimes} ARG;
751590SrgrimesARG	*arglist;				/* head of linked list */
761590Srgrimes
7760938SjakeLIST_HEAD(ttylisthead, ttytab) ttylist;
7811547Sdg
7911547Sdgstruct ttytab {
8036062Sjb	time_t	logout;				/* log out time */
811590Srgrimes	char	tty[UT_LINESIZE + 1];		/* terminal name */
8260938Sjake	LIST_ENTRY(ttytab) list;
8311547Sdg};
841590Srgrimes
851590Srgrimesstatic long	currentout,			/* current logout value */
861590Srgrimes		maxrec;				/* records to display */
871590Srgrimesstatic char	*file = _PATH_WTMP;		/* wtmp file */
8836434Sdannystatic int	sflag = 0;			/* show delta in seconds */
8936434Sdannystatic int	width = 5;			/* show seconds in delta */
901590Srgrimes
911590Srgrimesvoid	 addarg __P((int, char *));
921590Srgrimesvoid	 hostconv __P((char *));
931590Srgrimesvoid	 onintr __P((int));
941590Srgrimeschar	*ttyconv __P((char *));
9511547Sdgint	 want __P((struct utmp *));
961590Srgrimesvoid	 wtmp __P((void));
971590Srgrimes
9836434Sdannyvoid
9936434Sdannyusage(void)
10036434Sdanny{
10136434Sdanny	(void)fprintf(stderr,
10236434Sdanny	"usage: last [-#] [-f file] [-h hostname] [-t tty] [-s|w] [user ...]\n");
10336434Sdanny	exit(1);
10436434Sdanny}
10536434Sdanny
1061590Srgrimesint
1071590Srgrimesmain(argc, argv)
1081590Srgrimes	int argc;
1091590Srgrimes	char *argv[];
1101590Srgrimes{
1111590Srgrimes	extern int optind;
1121590Srgrimes	extern char *optarg;
1131590Srgrimes	int ch;
1141590Srgrimes	char *p;
1151590Srgrimes
11616438Sache	(void) setlocale(LC_TIME, "");
11716438Sache
1181590Srgrimes	maxrec = -1;
11936434Sdanny	while ((ch = getopt(argc, argv, "0123456789f:h:st:w")) != -1)
1201590Srgrimes		switch (ch) {
1211590Srgrimes		case '0': case '1': case '2': case '3': case '4':
1221590Srgrimes		case '5': case '6': case '7': case '8': case '9':
1231590Srgrimes			/*
1241590Srgrimes			 * kludge: last was originally designed to take
1251590Srgrimes			 * a number after a dash.
1261590Srgrimes			 */
1271590Srgrimes			if (maxrec == -1) {
1281590Srgrimes				p = argv[optind - 1];
1291590Srgrimes				if (p[0] == '-' && p[1] == ch && !p[2])
1301590Srgrimes					maxrec = atol(++p);
1311590Srgrimes				else
1321590Srgrimes					maxrec = atol(argv[optind] + 1);
1331590Srgrimes				if (!maxrec)
1341590Srgrimes					exit(0);
1351590Srgrimes			}
1361590Srgrimes			break;
1371590Srgrimes		case 'f':
1381590Srgrimes			file = optarg;
1391590Srgrimes			break;
1401590Srgrimes		case 'h':
1411590Srgrimes			hostconv(optarg);
1421590Srgrimes			addarg(HOST_TYPE, optarg);
1431590Srgrimes			break;
14436434Sdanny		case 's':
14536434Sdanny			sflag++;	/* Show delta as seconds */
14636434Sdanny			break;
1471590Srgrimes		case 't':
1481590Srgrimes			addarg(TTY_TYPE, ttyconv(optarg));
1491590Srgrimes			break;
15036434Sdanny		case 'w':
15136434Sdanny			width = 8;
15236434Sdanny			break;
1531590Srgrimes		case '?':
1541590Srgrimes		default:
15536434Sdanny			usage();
1561590Srgrimes		}
1571590Srgrimes
15836434Sdanny	if (sflag && width == 8) usage();
15936434Sdanny
1601590Srgrimes	if (argc) {
1611590Srgrimes		setlinebuf(stdout);
1621590Srgrimes		for (argv += optind; *argv; ++argv) {
1631590Srgrimes#define	COMPATIBILITY
1641590Srgrimes#ifdef	COMPATIBILITY
1651590Srgrimes			/* code to allow "last p5" to work */
1661590Srgrimes			addarg(TTY_TYPE, ttyconv(*argv));
1671590Srgrimes#endif
1681590Srgrimes			addarg(USER_TYPE, *argv);
1691590Srgrimes		}
1701590Srgrimes	}
1711590Srgrimes	wtmp();
1721590Srgrimes	exit(0);
1731590Srgrimes}
1741590Srgrimes
1751590Srgrimes/*
1761590Srgrimes * wtmp --
1771590Srgrimes *	read through the wtmp file
1781590Srgrimes */
1791590Srgrimesvoid
1801590Srgrimeswtmp()
1811590Srgrimes{
1821590Srgrimes	struct utmp	*bp;			/* current structure */
18319223Sjoerg	struct ttytab	*tt, *ttx;		/* ttylist entry */
1841590Srgrimes	struct stat	stb;			/* stat of file for size */
18536062Sjb	long	bl;
18636062Sjb	time_t	delta;				/* time difference */
1871590Srgrimes	int	bytes, wfd;
18816438Sache	char    *crmsg;
18916438Sache	char ct[80];
19016438Sache	struct tm *tm;
1911590Srgrimes
19211547Sdg	LIST_INIT(&ttylist);
19311547Sdg
1941590Srgrimes	if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1)
1951590Srgrimes		err(1, "%s", file);
1961590Srgrimes	bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf);
1971590Srgrimes
1981590Srgrimes	(void)time(&buf[0].ut_time);
1991590Srgrimes	(void)signal(SIGINT, onintr);
2001590Srgrimes	(void)signal(SIGQUIT, onintr);
2011590Srgrimes
2021590Srgrimes	while (--bl >= 0) {
2031590Srgrimes		if (lseek(wfd, (off_t)(bl * sizeof(buf)), L_SET) == -1 ||
2041590Srgrimes		    (bytes = read(wfd, buf, sizeof(buf))) == -1)
2051590Srgrimes			err(1, "%s", file);
2061590Srgrimes		for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) {
2071590Srgrimes			/*
2081590Srgrimes			 * if the terminal line is '~', the machine stopped.
2091590Srgrimes			 * see utmp(5) for more info.
2101590Srgrimes			 */
2111590Srgrimes			if (bp->ut_line[0] == '~' && !bp->ut_line[1]) {
2121590Srgrimes				/* everybody just logged out */
21319223Sjoerg				for (tt = ttylist.lh_first; tt;) {
21411547Sdg					LIST_REMOVE(tt, list);
21519223Sjoerg					ttx = tt;
21619223Sjoerg					tt = tt->list.le_next;
21719223Sjoerg					free(ttx);
21811547Sdg				}
2191590Srgrimes				currentout = -bp->ut_time;
2201590Srgrimes				crmsg = strncmp(bp->ut_name, "shutdown",
2211590Srgrimes				    UT_NAMESIZE) ? "crash" : "shutdown";
22211547Sdg				if (want(bp)) {
22316438Sache					tm = localtime(&bp->ut_time);
22416438Sache					(void) strftime(ct, sizeof(ct), "%c", tm);
22520158Sache					printf("%-*.*s %-*.*s %-*.*s %10.10s %5.5s \n",
2261590Srgrimes					    UT_NAMESIZE, UT_NAMESIZE,
2271590Srgrimes					    bp->ut_name, UT_LINESIZE,
2281590Srgrimes					    UT_LINESIZE, bp->ut_line,
2291590Srgrimes					    UT_HOSTSIZE, UT_HOSTSIZE,
2301590Srgrimes					    bp->ut_host, ct, ct + 11);
2311590Srgrimes					if (maxrec != -1 && !--maxrec)
2321590Srgrimes						return;
2331590Srgrimes				}
2341590Srgrimes				continue;
2351590Srgrimes			}
2361590Srgrimes			/*
2371590Srgrimes			 * if the line is '{' or '|', date got set; see
2381590Srgrimes			 * utmp(5) for more info.
2391590Srgrimes			 */
2401590Srgrimes			if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|')
2411590Srgrimes			    && !bp->ut_line[1]) {
24211547Sdg				if (want(bp)) {
24316438Sache					tm = localtime(&bp->ut_time);
24416438Sache					(void) strftime(ct, sizeof(ct), "%c", tm);
24520158Sache					printf("%-*.*s %-*.*s %-*.*s %10.10s %5.5s \n",
24611547Sdg					    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
24711547Sdg					    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
24811547Sdg					    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
24911547Sdg					    ct, ct + 11);
2501590Srgrimes					if (maxrec && !--maxrec)
2511590Srgrimes						return;
2521590Srgrimes				}
2531590Srgrimes				continue;
2541590Srgrimes			}
25511547Sdg			if (bp->ut_name[0] == '\0' || want(bp)) {
25611547Sdg				/* find associated tty */
25711547Sdg				for (tt = ttylist.lh_first; ; tt = tt->list.le_next) {
25811547Sdg					if (tt == NULL) {
25911547Sdg						/* add new one */
26011547Sdg						tt = malloc(sizeof(struct ttytab));
26111547Sdg						if (tt == NULL)
26211547Sdg							err(1, "malloc failure");
26311547Sdg						tt->logout = currentout;
26411547Sdg						strncpy(tt->tty, bp->ut_line, UT_LINESIZE);
26511547Sdg						LIST_INSERT_HEAD(&ttylist, tt, list);
26611547Sdg						break;
26711547Sdg					}
26811547Sdg					if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE))
26911547Sdg						break;
2701590Srgrimes				}
27111547Sdg				if (bp->ut_name[0]) {
27211547Sdg					/*
27311547Sdg					 * when uucp and ftp log in over a network, the entry in
27411547Sdg					 * the utmp file is the name plus their process id.  See
27511547Sdg					 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
27611547Sdg					 */
27711547Sdg					if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
27811547Sdg						bp->ut_line[3] = '\0';
27911547Sdg					else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
28011547Sdg						bp->ut_line[4] = '\0';
28116438Sache					tm = localtime(&bp->ut_time);
28216438Sache					(void) strftime(ct, sizeof(ct), "%c", tm);
28320158Sache					printf("%-*.*s %-*.*s %-*.*s %10.10s %5.5s ",
28411547Sdg					    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
28511547Sdg					    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
28611547Sdg					    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
28711547Sdg					    ct, ct + 11);
28811547Sdg					if (!tt->logout)
28911547Sdg						puts("  still logged in");
29011547Sdg					else {
29111547Sdg						if (tt->logout < 0) {
29211547Sdg							tt->logout = -tt->logout;
29311547Sdg							printf("- %s", crmsg);
29411547Sdg						}
29516438Sache						else {
29616438Sache							tm = localtime(&tt->logout);
29716438Sache							(void) strftime(ct, sizeof(ct), "%c", tm);
29816438Sache							printf("- %5.5s", ct + 11);
29916438Sache						}
30011547Sdg						delta = tt->logout - bp->ut_time;
30136434Sdanny						if ( sflag ) {
30236434Sdanny							printf("  (%8lu)\n",
30336434Sdanny								delta);
30436434Sdanny						} else {
30536434Sdanny						    tm = gmtime(&delta);
30636434Sdanny						    (void) strftime(ct, sizeof(ct), "%c", tm);
30736434Sdanny						    if (delta < 86400)
30836434Sdanny							printf("  (%*.*s)\n", width, width, ct + 11);
30936434Sdanny						    else
31036434Sdanny							printf(" (%ld+%*.*s)\n",
31136434Sdanny							    delta / 86400, width, width, ct + 11);
31236434Sdanny						}
3131590Srgrimes					}
31411547Sdg					LIST_REMOVE(tt, list);
31511547Sdg					free(tt);
31611547Sdg					if (maxrec != -1 && !--maxrec)
31711547Sdg						return;
31811547Sdg				} else {
31911547Sdg					tt->logout = bp->ut_time;
3201590Srgrimes				}
3211590Srgrimes			}
3221590Srgrimes		}
3231590Srgrimes	}
32416438Sache	tm = localtime(&buf[0].ut_time);
32535658Ssteve	(void) strftime(ct, sizeof(ct), "\nwtmp begins %c\n", tm);
32635658Ssteve	printf(ct);
3271590Srgrimes}
3281590Srgrimes
3291590Srgrimes/*
3301590Srgrimes * want --
3311590Srgrimes *	see if want this entry
3321590Srgrimes */
3331590Srgrimesint
33411547Sdgwant(bp)
3351590Srgrimes	struct utmp *bp;
3361590Srgrimes{
3371590Srgrimes	ARG *step;
3381590Srgrimes
3391590Srgrimes	if (!arglist)
3401590Srgrimes		return (YES);
3411590Srgrimes
3421590Srgrimes	for (step = arglist; step; step = step->next)
3431590Srgrimes		switch(step->type) {
3441590Srgrimes		case HOST_TYPE:
3451590Srgrimes			if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE))
3461590Srgrimes				return (YES);
3471590Srgrimes			break;
3481590Srgrimes		case TTY_TYPE:
3491590Srgrimes			if (!strncmp(step->name, bp->ut_line, UT_LINESIZE))
3501590Srgrimes				return (YES);
3511590Srgrimes			break;
3521590Srgrimes		case USER_TYPE:
3531590Srgrimes			if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE))
3541590Srgrimes				return (YES);
3551590Srgrimes			break;
3561590Srgrimes	}
3571590Srgrimes	return (NO);
3581590Srgrimes}
3591590Srgrimes
3601590Srgrimes/*
3611590Srgrimes * addarg --
3621590Srgrimes *	add an entry to a linked list of arguments
3631590Srgrimes */
3641590Srgrimesvoid
3651590Srgrimesaddarg(type, arg)
3661590Srgrimes	int type;
3671590Srgrimes	char *arg;
3681590Srgrimes{
3691590Srgrimes	ARG *cur;
3701590Srgrimes
3711590Srgrimes	if (!(cur = (ARG *)malloc((u_int)sizeof(ARG))))
3721590Srgrimes		err(1, "malloc failure");
3731590Srgrimes	cur->next = arglist;
3741590Srgrimes	cur->type = type;
3751590Srgrimes	cur->name = arg;
3761590Srgrimes	arglist = cur;
3771590Srgrimes}
3781590Srgrimes
3791590Srgrimes/*
3801590Srgrimes * hostconv --
3811590Srgrimes *	convert the hostname to search pattern; if the supplied host name
3821590Srgrimes *	has a domain attached that is the same as the current domain, rip
3831590Srgrimes *	off the domain suffix since that's what login(1) does.
3841590Srgrimes */
3851590Srgrimesvoid
3861590Srgrimeshostconv(arg)
3871590Srgrimes	char *arg;
3881590Srgrimes{
3891590Srgrimes	static int first = 1;
3901590Srgrimes	static char *hostdot, name[MAXHOSTNAMELEN];
3911590Srgrimes	char *argdot;
3921590Srgrimes
3931590Srgrimes	if (!(argdot = strchr(arg, '.')))
3941590Srgrimes		return;
3951590Srgrimes	if (first) {
3961590Srgrimes		first = 0;
3971590Srgrimes		if (gethostname(name, sizeof(name)))
3981590Srgrimes			err(1, "gethostname");
3991590Srgrimes		hostdot = strchr(name, '.');
4001590Srgrimes	}
4011590Srgrimes	if (hostdot && !strcasecmp(hostdot, argdot))
4021590Srgrimes		*argdot = '\0';
4031590Srgrimes}
4041590Srgrimes
4051590Srgrimes/*
4061590Srgrimes * ttyconv --
4071590Srgrimes *	convert tty to correct name.
4081590Srgrimes */
4091590Srgrimeschar *
4101590Srgrimesttyconv(arg)
4111590Srgrimes	char *arg;
4121590Srgrimes{
4131590Srgrimes	char *mval;
4141590Srgrimes
4151590Srgrimes	/*
4161590Srgrimes	 * kludge -- we assume that all tty's end with
4171590Srgrimes	 * a two character suffix.
4181590Srgrimes	 */
4191590Srgrimes	if (strlen(arg) == 2) {
4201590Srgrimes		/* either 6 for "ttyxx" or 8 for "console" */
4211590Srgrimes		if (!(mval = malloc((u_int)8)))
4221590Srgrimes			err(1, "malloc failure");
4231590Srgrimes		if (!strcmp(arg, "co"))
4241590Srgrimes			(void)strcpy(mval, "console");
4251590Srgrimes		else {
4261590Srgrimes			(void)strcpy(mval, "tty");
4271590Srgrimes			(void)strcpy(mval + 3, arg);
4281590Srgrimes		}
4291590Srgrimes		return (mval);
4301590Srgrimes	}
4311590Srgrimes	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
4321590Srgrimes		return (arg + 5);
4331590Srgrimes	return (arg);
4341590Srgrimes}
4351590Srgrimes
4361590Srgrimes/*
4371590Srgrimes * onintr --
4381590Srgrimes *	on interrupt, we inform the user how far we've gotten
4391590Srgrimes */
4401590Srgrimesvoid
4411590Srgrimesonintr(signo)
4421590Srgrimes	int signo;
4431590Srgrimes{
44416438Sache	char ct[80];
44516438Sache	struct tm *tm;
4461590Srgrimes
44716438Sache	tm = localtime(&buf[0].ut_time);
44816438Sache	(void) strftime(ct, sizeof(ct), "%c", tm);
4491590Srgrimes	printf("\ninterrupted %10.10s %5.5s \n", ct, ct + 11);
4501590Srgrimes	if (signo == SIGINT)
4511590Srgrimes		exit(1);
4521590Srgrimes	(void)fflush(stdout);			/* fix required for rsh */
4531590Srgrimes}
454