last.c revision 36062
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 {
7836062Sjb	time_t	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;
10724360Simp	while ((ch = getopt(argc, argv, "0123456789f:h:t:")) != -1)
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 */
16519223Sjoerg	struct ttytab	*tt, *ttx;		/* ttylist entry */
1661590Srgrimes	struct stat	stb;			/* stat of file for size */
16736062Sjb	long	bl;
16836062Sjb	time_t	delta;				/* time difference */
1691590Srgrimes	int	bytes, wfd;
17016438Sache	char    *crmsg;
17116438Sache	char ct[80];
17216438Sache	struct tm *tm;
1731590Srgrimes
17411547Sdg	LIST_INIT(&ttylist);
17511547Sdg
1761590Srgrimes	if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1)
1771590Srgrimes		err(1, "%s", file);
1781590Srgrimes	bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf);
1791590Srgrimes
1801590Srgrimes	(void)time(&buf[0].ut_time);
1811590Srgrimes	(void)signal(SIGINT, onintr);
1821590Srgrimes	(void)signal(SIGQUIT, onintr);
1831590Srgrimes
1841590Srgrimes	while (--bl >= 0) {
1851590Srgrimes		if (lseek(wfd, (off_t)(bl * sizeof(buf)), L_SET) == -1 ||
1861590Srgrimes		    (bytes = read(wfd, buf, sizeof(buf))) == -1)
1871590Srgrimes			err(1, "%s", file);
1881590Srgrimes		for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) {
1891590Srgrimes			/*
1901590Srgrimes			 * if the terminal line is '~', the machine stopped.
1911590Srgrimes			 * see utmp(5) for more info.
1921590Srgrimes			 */
1931590Srgrimes			if (bp->ut_line[0] == '~' && !bp->ut_line[1]) {
1941590Srgrimes				/* everybody just logged out */
19519223Sjoerg				for (tt = ttylist.lh_first; tt;) {
19611547Sdg					LIST_REMOVE(tt, list);
19719223Sjoerg					ttx = tt;
19819223Sjoerg					tt = tt->list.le_next;
19919223Sjoerg					free(ttx);
20011547Sdg				}
2011590Srgrimes				currentout = -bp->ut_time;
2021590Srgrimes				crmsg = strncmp(bp->ut_name, "shutdown",
2031590Srgrimes				    UT_NAMESIZE) ? "crash" : "shutdown";
20411547Sdg				if (want(bp)) {
20516438Sache					tm = localtime(&bp->ut_time);
20616438Sache					(void) strftime(ct, sizeof(ct), "%c", tm);
20720158Sache					printf("%-*.*s %-*.*s %-*.*s %10.10s %5.5s \n",
2081590Srgrimes					    UT_NAMESIZE, UT_NAMESIZE,
2091590Srgrimes					    bp->ut_name, UT_LINESIZE,
2101590Srgrimes					    UT_LINESIZE, bp->ut_line,
2111590Srgrimes					    UT_HOSTSIZE, UT_HOSTSIZE,
2121590Srgrimes					    bp->ut_host, ct, ct + 11);
2131590Srgrimes					if (maxrec != -1 && !--maxrec)
2141590Srgrimes						return;
2151590Srgrimes				}
2161590Srgrimes				continue;
2171590Srgrimes			}
2181590Srgrimes			/*
2191590Srgrimes			 * if the line is '{' or '|', date got set; see
2201590Srgrimes			 * utmp(5) for more info.
2211590Srgrimes			 */
2221590Srgrimes			if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|')
2231590Srgrimes			    && !bp->ut_line[1]) {
22411547Sdg				if (want(bp)) {
22516438Sache					tm = localtime(&bp->ut_time);
22616438Sache					(void) strftime(ct, sizeof(ct), "%c", tm);
22720158Sache					printf("%-*.*s %-*.*s %-*.*s %10.10s %5.5s \n",
22811547Sdg					    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
22911547Sdg					    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
23011547Sdg					    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
23111547Sdg					    ct, ct + 11);
2321590Srgrimes					if (maxrec && !--maxrec)
2331590Srgrimes						return;
2341590Srgrimes				}
2351590Srgrimes				continue;
2361590Srgrimes			}
23711547Sdg			if (bp->ut_name[0] == '\0' || want(bp)) {
23811547Sdg				/* find associated tty */
23911547Sdg				for (tt = ttylist.lh_first; ; tt = tt->list.le_next) {
24011547Sdg					if (tt == NULL) {
24111547Sdg						/* add new one */
24211547Sdg						tt = malloc(sizeof(struct ttytab));
24311547Sdg						if (tt == NULL)
24411547Sdg							err(1, "malloc failure");
24511547Sdg						tt->logout = currentout;
24611547Sdg						strncpy(tt->tty, bp->ut_line, UT_LINESIZE);
24711547Sdg						LIST_INSERT_HEAD(&ttylist, tt, list);
24811547Sdg						break;
24911547Sdg					}
25011547Sdg					if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE))
25111547Sdg						break;
2521590Srgrimes				}
25311547Sdg				if (bp->ut_name[0]) {
25411547Sdg					/*
25511547Sdg					 * when uucp and ftp log in over a network, the entry in
25611547Sdg					 * the utmp file is the name plus their process id.  See
25711547Sdg					 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
25811547Sdg					 */
25911547Sdg					if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
26011547Sdg						bp->ut_line[3] = '\0';
26111547Sdg					else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
26211547Sdg						bp->ut_line[4] = '\0';
26316438Sache					tm = localtime(&bp->ut_time);
26416438Sache					(void) strftime(ct, sizeof(ct), "%c", tm);
26520158Sache					printf("%-*.*s %-*.*s %-*.*s %10.10s %5.5s ",
26611547Sdg					    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
26711547Sdg					    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
26811547Sdg					    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
26911547Sdg					    ct, ct + 11);
27011547Sdg					if (!tt->logout)
27111547Sdg						puts("  still logged in");
27211547Sdg					else {
27311547Sdg						if (tt->logout < 0) {
27411547Sdg							tt->logout = -tt->logout;
27511547Sdg							printf("- %s", crmsg);
27611547Sdg						}
27716438Sache						else {
27816438Sache							tm = localtime(&tt->logout);
27916438Sache							(void) strftime(ct, sizeof(ct), "%c", tm);
28016438Sache							printf("- %5.5s", ct + 11);
28116438Sache						}
28211547Sdg						delta = tt->logout - bp->ut_time;
28316438Sache						tm = gmtime(&delta);
28416438Sache						(void) strftime(ct, sizeof(ct), "%c", tm);
28511547Sdg						if (delta < 86400)
28616438Sache							printf("  (%5.5s)\n", ct + 11);
28711547Sdg						else
28811547Sdg							printf(" (%ld+%5.5s)\n",
28916438Sache							    delta / 86400, ct + 11);
2901590Srgrimes					}
29111547Sdg					LIST_REMOVE(tt, list);
29211547Sdg					free(tt);
29311547Sdg					if (maxrec != -1 && !--maxrec)
29411547Sdg						return;
29511547Sdg				} else {
29611547Sdg					tt->logout = bp->ut_time;
2971590Srgrimes				}
2981590Srgrimes			}
2991590Srgrimes		}
3001590Srgrimes	}
30116438Sache	tm = localtime(&buf[0].ut_time);
30235658Ssteve	(void) strftime(ct, sizeof(ct), "\nwtmp begins %c\n", tm);
30335658Ssteve	printf(ct);
3041590Srgrimes}
3051590Srgrimes
3061590Srgrimes/*
3071590Srgrimes * want --
3081590Srgrimes *	see if want this entry
3091590Srgrimes */
3101590Srgrimesint
31111547Sdgwant(bp)
3121590Srgrimes	struct utmp *bp;
3131590Srgrimes{
3141590Srgrimes	ARG *step;
3151590Srgrimes
3161590Srgrimes	if (!arglist)
3171590Srgrimes		return (YES);
3181590Srgrimes
3191590Srgrimes	for (step = arglist; step; step = step->next)
3201590Srgrimes		switch(step->type) {
3211590Srgrimes		case HOST_TYPE:
3221590Srgrimes			if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE))
3231590Srgrimes				return (YES);
3241590Srgrimes			break;
3251590Srgrimes		case TTY_TYPE:
3261590Srgrimes			if (!strncmp(step->name, bp->ut_line, UT_LINESIZE))
3271590Srgrimes				return (YES);
3281590Srgrimes			break;
3291590Srgrimes		case USER_TYPE:
3301590Srgrimes			if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE))
3311590Srgrimes				return (YES);
3321590Srgrimes			break;
3331590Srgrimes	}
3341590Srgrimes	return (NO);
3351590Srgrimes}
3361590Srgrimes
3371590Srgrimes/*
3381590Srgrimes * addarg --
3391590Srgrimes *	add an entry to a linked list of arguments
3401590Srgrimes */
3411590Srgrimesvoid
3421590Srgrimesaddarg(type, arg)
3431590Srgrimes	int type;
3441590Srgrimes	char *arg;
3451590Srgrimes{
3461590Srgrimes	ARG *cur;
3471590Srgrimes
3481590Srgrimes	if (!(cur = (ARG *)malloc((u_int)sizeof(ARG))))
3491590Srgrimes		err(1, "malloc failure");
3501590Srgrimes	cur->next = arglist;
3511590Srgrimes	cur->type = type;
3521590Srgrimes	cur->name = arg;
3531590Srgrimes	arglist = cur;
3541590Srgrimes}
3551590Srgrimes
3561590Srgrimes/*
3571590Srgrimes * hostconv --
3581590Srgrimes *	convert the hostname to search pattern; if the supplied host name
3591590Srgrimes *	has a domain attached that is the same as the current domain, rip
3601590Srgrimes *	off the domain suffix since that's what login(1) does.
3611590Srgrimes */
3621590Srgrimesvoid
3631590Srgrimeshostconv(arg)
3641590Srgrimes	char *arg;
3651590Srgrimes{
3661590Srgrimes	static int first = 1;
3671590Srgrimes	static char *hostdot, name[MAXHOSTNAMELEN];
3681590Srgrimes	char *argdot;
3691590Srgrimes
3701590Srgrimes	if (!(argdot = strchr(arg, '.')))
3711590Srgrimes		return;
3721590Srgrimes	if (first) {
3731590Srgrimes		first = 0;
3741590Srgrimes		if (gethostname(name, sizeof(name)))
3751590Srgrimes			err(1, "gethostname");
3761590Srgrimes		hostdot = strchr(name, '.');
3771590Srgrimes	}
3781590Srgrimes	if (hostdot && !strcasecmp(hostdot, argdot))
3791590Srgrimes		*argdot = '\0';
3801590Srgrimes}
3811590Srgrimes
3821590Srgrimes/*
3831590Srgrimes * ttyconv --
3841590Srgrimes *	convert tty to correct name.
3851590Srgrimes */
3861590Srgrimeschar *
3871590Srgrimesttyconv(arg)
3881590Srgrimes	char *arg;
3891590Srgrimes{
3901590Srgrimes	char *mval;
3911590Srgrimes
3921590Srgrimes	/*
3931590Srgrimes	 * kludge -- we assume that all tty's end with
3941590Srgrimes	 * a two character suffix.
3951590Srgrimes	 */
3961590Srgrimes	if (strlen(arg) == 2) {
3971590Srgrimes		/* either 6 for "ttyxx" or 8 for "console" */
3981590Srgrimes		if (!(mval = malloc((u_int)8)))
3991590Srgrimes			err(1, "malloc failure");
4001590Srgrimes		if (!strcmp(arg, "co"))
4011590Srgrimes			(void)strcpy(mval, "console");
4021590Srgrimes		else {
4031590Srgrimes			(void)strcpy(mval, "tty");
4041590Srgrimes			(void)strcpy(mval + 3, arg);
4051590Srgrimes		}
4061590Srgrimes		return (mval);
4071590Srgrimes	}
4081590Srgrimes	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
4091590Srgrimes		return (arg + 5);
4101590Srgrimes	return (arg);
4111590Srgrimes}
4121590Srgrimes
4131590Srgrimes/*
4141590Srgrimes * onintr --
4151590Srgrimes *	on interrupt, we inform the user how far we've gotten
4161590Srgrimes */
4171590Srgrimesvoid
4181590Srgrimesonintr(signo)
4191590Srgrimes	int signo;
4201590Srgrimes{
42116438Sache	char ct[80];
42216438Sache	struct tm *tm;
4231590Srgrimes
42416438Sache	tm = localtime(&buf[0].ut_time);
42516438Sache	(void) strftime(ct, sizeof(ct), "%c", tm);
4261590Srgrimes	printf("\ninterrupted %10.10s %5.5s \n", ct, ct + 11);
4271590Srgrimes	if (signo == SIGINT)
4281590Srgrimes		exit(1);
4291590Srgrimes	(void)fflush(stdout);			/* fix required for rsh */
4301590Srgrimes}
431