last.c revision 16438
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.
321590Srgrimes */
331590Srgrimes
341590Srgrimes#ifndef lint
351590Srgrimesstatic char copyright[] =
361590Srgrimes"@(#) Copyright (c) 1987, 1993, 1994\n\
371590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
381590Srgrimes#endif /* not lint */
391590Srgrimes
401590Srgrimes#ifndef lint
411590Srgrimesstatic char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
421590Srgrimes#endif /* not lint */
431590Srgrimes
441590Srgrimes#include <sys/param.h>
451590Srgrimes#include <sys/stat.h>
461590Srgrimes
471590Srgrimes#include <err.h>
481590Srgrimes#include <fcntl.h>
4916438Sache#include <locale.h>
501590Srgrimes#include <paths.h>
511590Srgrimes#include <signal.h>
521590Srgrimes#include <stdio.h>
531590Srgrimes#include <stdlib.h>
541590Srgrimes#include <string.h>
551590Srgrimes#include <time.h>
561590Srgrimes#include <unistd.h>
571590Srgrimes#include <utmp.h>
5811547Sdg#include <sys/queue.h>
591590Srgrimes
601590Srgrimes#define	NO	0				/* false/no */
611590Srgrimes#define	YES	1				/* true/yes */
621590Srgrimes
631590Srgrimesstatic struct utmp	buf[1024];		/* utmp read buffer */
641590Srgrimes
651590Srgrimestypedef struct arg {
661590Srgrimes	char	*name;				/* argument */
671590Srgrimes#define	HOST_TYPE	-2
681590Srgrimes#define	TTY_TYPE	-3
691590Srgrimes#define	USER_TYPE	-4
701590Srgrimes	int	type;				/* type of arg */
711590Srgrimes	struct arg	*next;			/* linked list pointer */
721590Srgrimes} ARG;
731590SrgrimesARG	*arglist;				/* head of linked list */
741590Srgrimes
7511547SdgLIST_HEAD(ttylisthead, ttytab) ttylist;
7611547Sdg
7711547Sdgstruct ttytab {
781590Srgrimes	long	logout;				/* log out time */
791590Srgrimes	char	tty[UT_LINESIZE + 1];		/* terminal name */
8011547Sdg	LIST_ENTRY(ttytab) list;
8111547Sdg};
821590Srgrimes
831590Srgrimesstatic long	currentout,			/* current logout value */
841590Srgrimes		maxrec;				/* records to display */
851590Srgrimesstatic char	*file = _PATH_WTMP;		/* wtmp file */
861590Srgrimes
871590Srgrimesvoid	 addarg __P((int, char *));
881590Srgrimesvoid	 hostconv __P((char *));
891590Srgrimesvoid	 onintr __P((int));
901590Srgrimeschar	*ttyconv __P((char *));
9111547Sdgint	 want __P((struct utmp *));
921590Srgrimesvoid	 wtmp __P((void));
931590Srgrimes
941590Srgrimesint
951590Srgrimesmain(argc, argv)
961590Srgrimes	int argc;
971590Srgrimes	char *argv[];
981590Srgrimes{
991590Srgrimes	extern int optind;
1001590Srgrimes	extern char *optarg;
1011590Srgrimes	int ch;
1021590Srgrimes	char *p;
1031590Srgrimes
10416438Sache	(void) setlocale(LC_TIME, "");
10516438Sache
1061590Srgrimes	maxrec = -1;
1071590Srgrimes	while ((ch = getopt(argc, argv, "0123456789f:h:t:")) != EOF)
1081590Srgrimes		switch (ch) {
1091590Srgrimes		case '0': case '1': case '2': case '3': case '4':
1101590Srgrimes		case '5': case '6': case '7': case '8': case '9':
1111590Srgrimes			/*
1121590Srgrimes			 * kludge: last was originally designed to take
1131590Srgrimes			 * a number after a dash.
1141590Srgrimes			 */
1151590Srgrimes			if (maxrec == -1) {
1161590Srgrimes				p = argv[optind - 1];
1171590Srgrimes				if (p[0] == '-' && p[1] == ch && !p[2])
1181590Srgrimes					maxrec = atol(++p);
1191590Srgrimes				else
1201590Srgrimes					maxrec = atol(argv[optind] + 1);
1211590Srgrimes				if (!maxrec)
1221590Srgrimes					exit(0);
1231590Srgrimes			}
1241590Srgrimes			break;
1251590Srgrimes		case 'f':
1261590Srgrimes			file = optarg;
1271590Srgrimes			break;
1281590Srgrimes		case 'h':
1291590Srgrimes			hostconv(optarg);
1301590Srgrimes			addarg(HOST_TYPE, optarg);
1311590Srgrimes			break;
1321590Srgrimes		case 't':
1331590Srgrimes			addarg(TTY_TYPE, ttyconv(optarg));
1341590Srgrimes			break;
1351590Srgrimes		case '?':
1361590Srgrimes		default:
1371590Srgrimes			(void)fprintf(stderr,
1381590Srgrimes	"usage: last [-#] [-f file] [-t tty] [-h hostname] [user ...]\n");
1391590Srgrimes			exit(1);
1401590Srgrimes		}
1411590Srgrimes
1421590Srgrimes	if (argc) {
1431590Srgrimes		setlinebuf(stdout);
1441590Srgrimes		for (argv += optind; *argv; ++argv) {
1451590Srgrimes#define	COMPATIBILITY
1461590Srgrimes#ifdef	COMPATIBILITY
1471590Srgrimes			/* code to allow "last p5" to work */
1481590Srgrimes			addarg(TTY_TYPE, ttyconv(*argv));
1491590Srgrimes#endif
1501590Srgrimes			addarg(USER_TYPE, *argv);
1511590Srgrimes		}
1521590Srgrimes	}
1531590Srgrimes	wtmp();
1541590Srgrimes	exit(0);
1551590Srgrimes}
1561590Srgrimes
1571590Srgrimes/*
1581590Srgrimes * wtmp --
1591590Srgrimes *	read through the wtmp file
1601590Srgrimes */
1611590Srgrimesvoid
1621590Srgrimeswtmp()
1631590Srgrimes{
1641590Srgrimes	struct utmp	*bp;			/* current structure */
16511547Sdg	struct ttytab	*tt;				/* ttylist entry */
1661590Srgrimes	struct stat	stb;			/* stat of file for size */
1671590Srgrimes	long	bl, delta;			/* time difference */
1681590Srgrimes	int	bytes, wfd;
16916438Sache	char    *crmsg;
17016438Sache	char ct[80];
17116438Sache	struct tm *tm;
1721590Srgrimes
17311547Sdg	LIST_INIT(&ttylist);
17411547Sdg
1751590Srgrimes	if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1)
1761590Srgrimes		err(1, "%s", file);
1771590Srgrimes	bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf);
1781590Srgrimes
1791590Srgrimes	(void)time(&buf[0].ut_time);
1801590Srgrimes	(void)signal(SIGINT, onintr);
1811590Srgrimes	(void)signal(SIGQUIT, onintr);
1821590Srgrimes
1831590Srgrimes	while (--bl >= 0) {
1841590Srgrimes		if (lseek(wfd, (off_t)(bl * sizeof(buf)), L_SET) == -1 ||
1851590Srgrimes		    (bytes = read(wfd, buf, sizeof(buf))) == -1)
1861590Srgrimes			err(1, "%s", file);
1871590Srgrimes		for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) {
1881590Srgrimes			/*
1891590Srgrimes			 * if the terminal line is '~', the machine stopped.
1901590Srgrimes			 * see utmp(5) for more info.
1911590Srgrimes			 */
1921590Srgrimes			if (bp->ut_line[0] == '~' && !bp->ut_line[1]) {
1931590Srgrimes				/* everybody just logged out */
19411547Sdg				for (tt = ttylist.lh_first; tt; tt = tt->list.le_next) {
19511547Sdg					LIST_REMOVE(tt, list);
19611547Sdg					free(tt);
19711547Sdg				}
1981590Srgrimes				currentout = -bp->ut_time;
1991590Srgrimes				crmsg = strncmp(bp->ut_name, "shutdown",
2001590Srgrimes				    UT_NAMESIZE) ? "crash" : "shutdown";
20111547Sdg				if (want(bp)) {
20216438Sache					tm = localtime(&bp->ut_time);
20316438Sache					(void) strftime(ct, sizeof(ct), "%c", tm);
20411547Sdg					printf("%-*.*s  %-*.*s %-*.*s %10.10s %5.5s \n",
2051590Srgrimes					    UT_NAMESIZE, UT_NAMESIZE,
2061590Srgrimes					    bp->ut_name, UT_LINESIZE,
2071590Srgrimes					    UT_LINESIZE, bp->ut_line,
2081590Srgrimes					    UT_HOSTSIZE, UT_HOSTSIZE,
2091590Srgrimes					    bp->ut_host, ct, ct + 11);
2101590Srgrimes					if (maxrec != -1 && !--maxrec)
2111590Srgrimes						return;
2121590Srgrimes				}
2131590Srgrimes				continue;
2141590Srgrimes			}
2151590Srgrimes			/*
2161590Srgrimes			 * if the line is '{' or '|', date got set; see
2171590Srgrimes			 * utmp(5) for more info.
2181590Srgrimes			 */
2191590Srgrimes			if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|')
2201590Srgrimes			    && !bp->ut_line[1]) {
22111547Sdg				if (want(bp)) {
22216438Sache					tm = localtime(&bp->ut_time);
22316438Sache					(void) strftime(ct, sizeof(ct), "%c", tm);
22411547Sdg					printf("%-*.*s  %-*.*s %-*.*s %10.10s %5.5s \n",
22511547Sdg					    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
22611547Sdg					    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
22711547Sdg					    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
22811547Sdg					    ct, ct + 11);
2291590Srgrimes					if (maxrec && !--maxrec)
2301590Srgrimes						return;
2311590Srgrimes				}
2321590Srgrimes				continue;
2331590Srgrimes			}
23411547Sdg			if (bp->ut_name[0] == '\0' || want(bp)) {
23511547Sdg				/* find associated tty */
23611547Sdg				for (tt = ttylist.lh_first; ; tt = tt->list.le_next) {
23711547Sdg					if (tt == NULL) {
23811547Sdg						/* add new one */
23911547Sdg						tt = malloc(sizeof(struct ttytab));
24011547Sdg						if (tt == NULL)
24111547Sdg							err(1, "malloc failure");
24211547Sdg						tt->logout = currentout;
24311547Sdg						strncpy(tt->tty, bp->ut_line, UT_LINESIZE);
24411547Sdg						LIST_INSERT_HEAD(&ttylist, tt, list);
24511547Sdg						break;
24611547Sdg					}
24711547Sdg					if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE))
24811547Sdg						break;
2491590Srgrimes				}
25011547Sdg				if (bp->ut_name[0]) {
25111547Sdg					/*
25211547Sdg					 * when uucp and ftp log in over a network, the entry in
25311547Sdg					 * the utmp file is the name plus their process id.  See
25411547Sdg					 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
25511547Sdg					 */
25611547Sdg					if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
25711547Sdg						bp->ut_line[3] = '\0';
25811547Sdg					else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
25911547Sdg						bp->ut_line[4] = '\0';
26016438Sache					tm = localtime(&bp->ut_time);
26116438Sache					(void) strftime(ct, sizeof(ct), "%c", tm);
26211547Sdg					printf("%-*.*s  %-*.*s %-*.*s %10.10s %5.5s ",
26311547Sdg					    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
26411547Sdg					    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
26511547Sdg					    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
26611547Sdg					    ct, ct + 11);
26711547Sdg					if (!tt->logout)
26811547Sdg						puts("  still logged in");
26911547Sdg					else {
27011547Sdg						if (tt->logout < 0) {
27111547Sdg							tt->logout = -tt->logout;
27211547Sdg							printf("- %s", crmsg);
27311547Sdg						}
27416438Sache						else {
27516438Sache							tm = localtime(&tt->logout);
27616438Sache							(void) strftime(ct, sizeof(ct), "%c", tm);
27716438Sache							printf("- %5.5s", ct + 11);
27816438Sache						}
27911547Sdg						delta = tt->logout - bp->ut_time;
28016438Sache						tm = gmtime(&delta);
28116438Sache						(void) strftime(ct, sizeof(ct), "%c", tm);
28211547Sdg						if (delta < 86400)
28316438Sache							printf("  (%5.5s)\n", ct + 11);
28411547Sdg						else
28511547Sdg							printf(" (%ld+%5.5s)\n",
28616438Sache							    delta / 86400, ct + 11);
2871590Srgrimes					}
28811547Sdg					LIST_REMOVE(tt, list);
28911547Sdg					free(tt);
29011547Sdg					if (maxrec != -1 && !--maxrec)
29111547Sdg						return;
29211547Sdg				} else {
29311547Sdg					tt->logout = bp->ut_time;
2941590Srgrimes				}
2951590Srgrimes			}
2961590Srgrimes		}
2971590Srgrimes	}
29816438Sache	tm = localtime(&buf[0].ut_time);
29916438Sache	(void) strftime(ct, sizeof(ct), "%c", tm);
3001590Srgrimes	printf("\nwtmp begins %10.10s %5.5s \n", ct, ct + 11);
3011590Srgrimes}
3021590Srgrimes
3031590Srgrimes/*
3041590Srgrimes * want --
3051590Srgrimes *	see if want this entry
3061590Srgrimes */
3071590Srgrimesint
30811547Sdgwant(bp)
3091590Srgrimes	struct utmp *bp;
3101590Srgrimes{
3111590Srgrimes	ARG *step;
3121590Srgrimes
3131590Srgrimes	if (!arglist)
3141590Srgrimes		return (YES);
3151590Srgrimes
3161590Srgrimes	for (step = arglist; step; step = step->next)
3171590Srgrimes		switch(step->type) {
3181590Srgrimes		case HOST_TYPE:
3191590Srgrimes			if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE))
3201590Srgrimes				return (YES);
3211590Srgrimes			break;
3221590Srgrimes		case TTY_TYPE:
3231590Srgrimes			if (!strncmp(step->name, bp->ut_line, UT_LINESIZE))
3241590Srgrimes				return (YES);
3251590Srgrimes			break;
3261590Srgrimes		case USER_TYPE:
3271590Srgrimes			if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE))
3281590Srgrimes				return (YES);
3291590Srgrimes			break;
3301590Srgrimes	}
3311590Srgrimes	return (NO);
3321590Srgrimes}
3331590Srgrimes
3341590Srgrimes/*
3351590Srgrimes * addarg --
3361590Srgrimes *	add an entry to a linked list of arguments
3371590Srgrimes */
3381590Srgrimesvoid
3391590Srgrimesaddarg(type, arg)
3401590Srgrimes	int type;
3411590Srgrimes	char *arg;
3421590Srgrimes{
3431590Srgrimes	ARG *cur;
3441590Srgrimes
3451590Srgrimes	if (!(cur = (ARG *)malloc((u_int)sizeof(ARG))))
3461590Srgrimes		err(1, "malloc failure");
3471590Srgrimes	cur->next = arglist;
3481590Srgrimes	cur->type = type;
3491590Srgrimes	cur->name = arg;
3501590Srgrimes	arglist = cur;
3511590Srgrimes}
3521590Srgrimes
3531590Srgrimes/*
3541590Srgrimes * hostconv --
3551590Srgrimes *	convert the hostname to search pattern; if the supplied host name
3561590Srgrimes *	has a domain attached that is the same as the current domain, rip
3571590Srgrimes *	off the domain suffix since that's what login(1) does.
3581590Srgrimes */
3591590Srgrimesvoid
3601590Srgrimeshostconv(arg)
3611590Srgrimes	char *arg;
3621590Srgrimes{
3631590Srgrimes	static int first = 1;
3641590Srgrimes	static char *hostdot, name[MAXHOSTNAMELEN];
3651590Srgrimes	char *argdot;
3661590Srgrimes
3671590Srgrimes	if (!(argdot = strchr(arg, '.')))
3681590Srgrimes		return;
3691590Srgrimes	if (first) {
3701590Srgrimes		first = 0;
3711590Srgrimes		if (gethostname(name, sizeof(name)))
3721590Srgrimes			err(1, "gethostname");
3731590Srgrimes		hostdot = strchr(name, '.');
3741590Srgrimes	}
3751590Srgrimes	if (hostdot && !strcasecmp(hostdot, argdot))
3761590Srgrimes		*argdot = '\0';
3771590Srgrimes}
3781590Srgrimes
3791590Srgrimes/*
3801590Srgrimes * ttyconv --
3811590Srgrimes *	convert tty to correct name.
3821590Srgrimes */
3831590Srgrimeschar *
3841590Srgrimesttyconv(arg)
3851590Srgrimes	char *arg;
3861590Srgrimes{
3871590Srgrimes	char *mval;
3881590Srgrimes
3891590Srgrimes	/*
3901590Srgrimes	 * kludge -- we assume that all tty's end with
3911590Srgrimes	 * a two character suffix.
3921590Srgrimes	 */
3931590Srgrimes	if (strlen(arg) == 2) {
3941590Srgrimes		/* either 6 for "ttyxx" or 8 for "console" */
3951590Srgrimes		if (!(mval = malloc((u_int)8)))
3961590Srgrimes			err(1, "malloc failure");
3971590Srgrimes		if (!strcmp(arg, "co"))
3981590Srgrimes			(void)strcpy(mval, "console");
3991590Srgrimes		else {
4001590Srgrimes			(void)strcpy(mval, "tty");
4011590Srgrimes			(void)strcpy(mval + 3, arg);
4021590Srgrimes		}
4031590Srgrimes		return (mval);
4041590Srgrimes	}
4051590Srgrimes	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
4061590Srgrimes		return (arg + 5);
4071590Srgrimes	return (arg);
4081590Srgrimes}
4091590Srgrimes
4101590Srgrimes/*
4111590Srgrimes * onintr --
4121590Srgrimes *	on interrupt, we inform the user how far we've gotten
4131590Srgrimes */
4141590Srgrimesvoid
4151590Srgrimesonintr(signo)
4161590Srgrimes	int signo;
4171590Srgrimes{
41816438Sache	char ct[80];
41916438Sache	struct tm *tm;
4201590Srgrimes
42116438Sache	tm = localtime(&buf[0].ut_time);
42216438Sache	(void) strftime(ct, sizeof(ct), "%c", tm);
4231590Srgrimes	printf("\ninterrupted %10.10s %5.5s \n", ct, ct + 11);
4241590Srgrimes	if (signo == SIGINT)
4251590Srgrimes		exit(1);
4261590Srgrimes	(void)fflush(stdout);			/* fix required for rsh */
4271590Srgrimes}
428