last.c revision 74588
1155192Srwatson/*
2155192Srwatson * Copyright (c) 1987, 1993, 1994
3170407Srwatson *	The Regents of the University of California.  All rights reserved.
4155192Srwatson *
5155192Srwatson * Redistribution and use in source and binary forms, with or without
6155192Srwatson * modification, are permitted provided that the following conditions
7155192Srwatson * are met:
8155192Srwatson * 1. Redistributions of source code must retain the above copyright
9155192Srwatson *    notice, this list of conditions and the following disclaimer.
10155192Srwatson * 2. Redistributions in binary form must reproduce the above copyright
11155192Srwatson *    notice, this list of conditions and the following disclaimer in the
12155192Srwatson *    documentation and/or other materials provided with the distribution.
13155192Srwatson * 3. All advertising materials mentioning features or use of this software
14155192Srwatson *    must display the following acknowledgement:
15155192Srwatson *	This product includes software developed by the University of
16155192Srwatson *	California, Berkeley and its contributors.
17155192Srwatson * 4. Neither the name of the University nor the names of its contributors
18155192Srwatson *    may be used to endorse or promote products derived from this software
19155192Srwatson *    without specific prior written permission.
20155192Srwatson *
21155192Srwatson * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22155192Srwatson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23155192Srwatson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24155192Srwatson * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25155192Srwatson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26155192Srwatson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27155192Srwatson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28155192Srwatson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29155192Srwatson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30155192Srwatson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31155192Srwatson * SUCH DAMAGE.
32155192Srwatson *
33155192Srwatson * $FreeBSD: head/usr.bin/last/last.c 74588 2001-03-21 19:08:01Z ache $
34155192Srwatson */
35155192Srwatson
36155192Srwatson#ifndef lint
37155192Srwatsonstatic char copyright[] =
38155192Srwatson"@(#) Copyright (c) 1987, 1993, 1994\n\
39155192Srwatson	The Regents of the University of California.  All rights reserved.\n";
40155192Srwatson#endif /* not lint */
41155192Srwatson
42155192Srwatson#ifndef lint
43155192Srwatsonstatic char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
44155192Srwatson#endif /* not lint */
45164033Srwatson
46155192Srwatson#include <sys/param.h>
47155192Srwatson#include <sys/stat.h>
48155192Srwatson
49155192Srwatson#include <err.h>
50155192Srwatson#include <fcntl.h>
51155192Srwatson#include <langinfo.h>
52171144Srwatson#include <locale.h>
53155192Srwatson#include <paths.h>
54155192Srwatson#include <signal.h>
55155192Srwatson#include <stdio.h>
56155192Srwatson#include <stdlib.h>
57155192Srwatson#include <string.h>
58155192Srwatson#include <time.h>
59155192Srwatson#include <unistd.h>
60155192Srwatson#include <utmp.h>
61155192Srwatson#include <sys/queue.h>
62155406Srwatson
63156291Srwatson#define	NO	0				/* false/no */
64155406Srwatson#define	YES	1				/* true/yes */
65155406Srwatson
66155192Srwatsonstatic struct utmp	buf[1024];		/* utmp read buffer */
67155192Srwatson
68155192Srwatsontypedef struct arg {
69155192Srwatson	char	*name;				/* argument */
70155192Srwatson#define	HOST_TYPE	-2
71155192Srwatson#define	TTY_TYPE	-3
72155406Srwatson#define	USER_TYPE	-4
73155406Srwatson	int	type;				/* type of arg */
74156888Srwatson	struct arg	*next;			/* linked list pointer */
75170407Srwatson} ARG;
76155192SrwatsonARG	*arglist;				/* head of linked list */
77155192Srwatson
78155192SrwatsonLIST_HEAD(ttylisthead, ttytab) ttylist;
79155192Srwatson
80171144Srwatsonstruct ttytab {
81171144Srwatson	time_t	logout;				/* log out time */
82171144Srwatson	char	tty[UT_LINESIZE + 1];		/* terminal name */
83155192Srwatson	LIST_ENTRY(ttytab) list;
84170196Srwatson};
85170196Srwatson
86170196Srwatsonstatic long	currentout,			/* current logout value */
87155192Srwatson		maxrec;				/* records to display */
88155192Srwatsonstatic char	*file = _PATH_WTMP;		/* wtmp file */
89156889Srwatsonstatic int	sflag = 0;			/* show delta in seconds */
90156889Srwatsonstatic int	width = 5;			/* show seconds in delta */
91155192Srwatsonstatic int      d_first;
92155192Srwatson
93162176Srwatsonvoid	 addarg __P((int, char *));
94162176Srwatsonvoid	 hostconv __P((char *));
95155192Srwatsonvoid	 onintr __P((int));
96156889Srwatsonchar	*ttyconv __P((char *));
97156889Srwatsonint	 want __P((struct utmp *));
98161813Swsalamonvoid	 wtmp __P((void));
99161813Swsalamon
100155192Srwatsonvoid
101155192Srwatsonusage(void)
102155192Srwatson{
103155192Srwatson	(void)fprintf(stderr,
104156889Srwatson	"usage: last [-#] [-f file] [-h hostname] [-t tty] [-s|w] [user ...]\n");
105155192Srwatson	exit(1);
106155192Srwatson}
107170691Srwatson
108155192Srwatsonint
109156889Srwatsonmain(argc, argv)
110155192Srwatson	int argc;
111155192Srwatson	char *argv[];
112155192Srwatson{
113155192Srwatson	int ch;
114156889Srwatson	char *p;
115155192Srwatson
116155192Srwatson	(void) setlocale(LC_TIME, "");
117155192Srwatson	d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
118155192Srwatson
119155192Srwatson	maxrec = -1;
120156889Srwatson	while ((ch = getopt(argc, argv, "0123456789f:h:st:w")) != -1)
121155192Srwatson		switch (ch) {
122155192Srwatson		case '0': case '1': case '2': case '3': case '4':
123170196Srwatson		case '5': case '6': case '7': case '8': case '9':
124170196Srwatson			/*
125170196Srwatson			 * kludge: last was originally designed to take
126170196Srwatson			 * a number after a dash.
127170196Srwatson			 */
128170196Srwatson			if (maxrec == -1) {
129155192Srwatson				p = argv[optind - 1];
130156889Srwatson				if (p[0] == '-' && p[1] == ch && !p[2])
131156889Srwatson					maxrec = atol(++p);
132156889Srwatson				else
133155192Srwatson					maxrec = atol(argv[optind] + 1);
134155192Srwatson				if (!maxrec)
135155192Srwatson					exit(0);
136155192Srwatson			}
137156889Srwatson			break;
138155192Srwatson		case 'f':
139155192Srwatson			file = optarg;
140170196Srwatson			break;
141170196Srwatson		case 'h':
142155192Srwatson			hostconv(optarg);
143159261Srwatson			addarg(HOST_TYPE, optarg);
144155192Srwatson			break;
145155192Srwatson		case 's':
146159261Srwatson			sflag++;	/* Show delta as seconds */
147159261Srwatson			break;
148159261Srwatson		case 't':
149155192Srwatson			addarg(TTY_TYPE, ttyconv(optarg));
150159261Srwatson			break;
151155192Srwatson		case 'w':
152156889Srwatson			width = 8;
153156889Srwatson			break;
154170196Srwatson		case '?':
155170196Srwatson		default:
156155192Srwatson			usage();
157156889Srwatson		}
158155192Srwatson
159155192Srwatson	if (sflag && width == 8) usage();
160155406Srwatson
161155192Srwatson	if (argc) {
162155406Srwatson		setlinebuf(stdout);
163155406Srwatson		for (argv += optind; *argv; ++argv) {
164155406Srwatson#define	COMPATIBILITY
165155406Srwatson#ifdef	COMPATIBILITY
166155406Srwatson			/* code to allow "last p5" to work */
167155406Srwatson			addarg(TTY_TYPE, ttyconv(*argv));
168155406Srwatson#endif
169155406Srwatson			addarg(USER_TYPE, *argv);
170155406Srwatson		}
171155406Srwatson	}
172155406Srwatson	wtmp();
173155406Srwatson	exit(0);
174155406Srwatson}
175155406Srwatson
176155406Srwatson/*
177155406Srwatson * wtmp --
178155406Srwatson *	read through the wtmp file
179155406Srwatson */
180155406Srwatsonvoid
181155406Srwatsonwtmp()
182155406Srwatson{
183170407Srwatson	struct utmp	*bp;			/* current structure */
184170407Srwatson	struct ttytab	*tt, *ttx;		/* ttylist entry */
185155406Srwatson	struct stat	stb;			/* stat of file for size */
186170407Srwatson	long	bl;
187170407Srwatson	time_t	delta;				/* time difference */
188155406Srwatson	int	bytes, wfd;
189155406Srwatson	char    *crmsg;
190155406Srwatson	char ct[80];
191155192Srwatson	struct tm *tm;
192155406Srwatson
193155192Srwatson	LIST_INIT(&ttylist);
194155406Srwatson
195155192Srwatson	if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1)
196155406Srwatson		err(1, "%s", file);
197155406Srwatson	bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf);
198155406Srwatson
199155406Srwatson	(void)time(&buf[0].ut_time);
200155192Srwatson	(void)signal(SIGINT, onintr);
201155406Srwatson	(void)signal(SIGQUIT, onintr);
202155192Srwatson
203155406Srwatson	while (--bl >= 0) {
204155192Srwatson		if (lseek(wfd, (off_t)(bl * sizeof(buf)), L_SET) == -1 ||
205155406Srwatson		    (bytes = read(wfd, buf, sizeof(buf))) == -1)
206155192Srwatson			err(1, "%s", file);
207161813Swsalamon		for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) {
208161813Swsalamon			/*
209161813Swsalamon			 * if the terminal line is '~', the machine stopped.
210161813Swsalamon			 * see utmp(5) for more info.
211155192Srwatson			 */
212155192Srwatson			if (bp->ut_line[0] == '~' && !bp->ut_line[1]) {
213155192Srwatson				/* everybody just logged out */
214155192Srwatson				for (tt = LIST_FIRST(&ttylist); tt;) {
215155192Srwatson					LIST_REMOVE(tt, list);
216155192Srwatson					ttx = tt;
217155192Srwatson					tt = LIST_NEXT(tt, list);
218155192Srwatson					free(ttx);
219155192Srwatson				}
220155192Srwatson				currentout = -bp->ut_time;
221155192Srwatson				crmsg = strncmp(bp->ut_name, "shutdown",
222155192Srwatson				    UT_NAMESIZE) ? "crash" : "shutdown";
223155192Srwatson				if (want(bp)) {
224155192Srwatson					tm = localtime(&bp->ut_time);
225155192Srwatson					(void) strftime(ct, sizeof(ct),
226155192Srwatson						     d_first ? "%a %e %b %R" :
227161813Swsalamon							       "%a %b %e %R",
228161813Swsalamon						     tm);
229155192Srwatson					printf("%-*.*s %-*.*s %-*.*s %s\n",
230170196Srwatson					    UT_NAMESIZE, UT_NAMESIZE,
231156889Srwatson					    bp->ut_name, UT_LINESIZE,
232173142Srwatson					    UT_LINESIZE, bp->ut_line,
233173142Srwatson					    UT_HOSTSIZE, UT_HOSTSIZE,
234155192Srwatson					    bp->ut_host, ct);
235155192Srwatson					if (maxrec != -1 && !--maxrec)
236155192Srwatson						return;
237155192Srwatson				}
238155192Srwatson				continue;
239155192Srwatson			}
240155192Srwatson			/*
241155192Srwatson			 * if the line is '{' or '|', date got set; see
242155192Srwatson			 * utmp(5) for more info.
243155192Srwatson			 */
244159261Srwatson			if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|')
245159261Srwatson			    && !bp->ut_line[1]) {
246155192Srwatson				if (want(bp)) {
247155192Srwatson					tm = localtime(&bp->ut_time);
248159266Srwatson					(void) strftime(ct, sizeof(ct),
249155406Srwatson						     d_first ? "%a %e %b %R" :
250155406Srwatson							       "%a %b %e %R",
251155406Srwatson						     tm);
252155192Srwatson					printf("%-*.*s %-*.*s %-*.*s %s\n",
253155192Srwatson					    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
254155192Srwatson					    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
255155192Srwatson					    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
256155192Srwatson					    ct);
257155192Srwatson					if (maxrec && !--maxrec)
258155192Srwatson						return;
259155192Srwatson				}
260155192Srwatson				continue;
261156888Srwatson			}
262156888Srwatson			if (bp->ut_name[0] == '\0' || want(bp)) {
263155192Srwatson				/* find associated tty */
264155192Srwatson				LIST_FOREACH(tt, &ttylist, list)
265155192Srwatson					if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE))
266155192Srwatson						break;
267155192Srwatson
268155192Srwatson				if (tt == NULL) {
269155192Srwatson					/* add new one */
270155192Srwatson					tt = malloc(sizeof(struct ttytab));
271155192Srwatson					if (tt == NULL)
272155192Srwatson						err(1, "malloc failure");
273155192Srwatson					tt->logout = currentout;
274155192Srwatson					strncpy(tt->tty, bp->ut_line, UT_LINESIZE);
275155192Srwatson					LIST_INSERT_HEAD(&ttylist, tt, list);
276155192Srwatson				}
277155192Srwatson
278155192Srwatson				if (bp->ut_name[0]) {
279155192Srwatson					/*
280155192Srwatson					 * when uucp and ftp log in over a network, the entry in
281155192Srwatson					 * the utmp file is the name plus their process id.  See
282169896Srwatson					 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
283155192Srwatson					 */
284155192Srwatson					if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
285155192Srwatson						bp->ut_line[3] = '\0';
286155192Srwatson					else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
287155192Srwatson						bp->ut_line[4] = '\0';
288155192Srwatson					tm = localtime(&bp->ut_time);
289155192Srwatson					(void) strftime(ct, sizeof(ct),
290155192Srwatson						     d_first ? "%a %e %b %R" :
291155192Srwatson							       "%a %b %e %R",
292155192Srwatson						     tm);
293155192Srwatson					printf("%-*.*s %-*.*s %-*.*s %s ",
294155192Srwatson					    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
295155192Srwatson					    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
296155192Srwatson					    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
297155192Srwatson					    ct);
298155192Srwatson					if (!tt->logout)
299155192Srwatson						puts("  still logged in");
300155192Srwatson					else {
301155192Srwatson						if (tt->logout < 0) {
302155192Srwatson							tt->logout = -tt->logout;
303155406Srwatson							printf("- %s", crmsg);
304155406Srwatson						}
305155406Srwatson						else {
306155406Srwatson							tm = localtime(&tt->logout);
307155406Srwatson							(void) strftime(ct, sizeof(ct), "%R", tm);
308155192Srwatson							printf("- %s", ct);
309155192Srwatson						}
310165604Srwatson						delta = tt->logout - bp->ut_time;
311165604Srwatson						if ( sflag ) {
312165604Srwatson							printf("  (%8lu)\n",
313155192Srwatson								delta);
314155406Srwatson						} else {
315155406Srwatson						    tm = gmtime(&delta);
316155192Srwatson						    (void) strftime(ct, sizeof(ct),
317155192Srwatson						     width >= 8 ? "%T" : "%R",
318155192Srwatson						     tm);
319155192Srwatson						    if (delta < 86400)
320155192Srwatson							printf("  (%s)\n", ct);
321155192Srwatson						    else
322155192Srwatson							printf(" (%ld+%s)\n",
323155192Srwatson							    delta / 86400,  ct);
324156888Srwatson						}
325156888Srwatson					}
326156888Srwatson					LIST_REMOVE(tt, list);
327156888Srwatson					free(tt);
328156888Srwatson					if (maxrec != -1 && !--maxrec)
329156888Srwatson						return;
330156888Srwatson				} else {
331155192Srwatson					tt->logout = bp->ut_time;
332155192Srwatson				}
333155192Srwatson			}
334159269Srwatson		}
335159269Srwatson	}
336159269Srwatson	tm = localtime(&buf[0].ut_time);
337155192Srwatson	(void) strftime(ct, sizeof(ct), "\nwtmp begins %c\n", tm);
338155192Srwatson	printf("%s", ct);
339155192Srwatson}
340155192Srwatson
341155192Srwatson/*
342155192Srwatson * want --
343155192Srwatson *	see if want this entry
344170196Srwatson */
345170196Srwatsonint
346155192Srwatsonwant(bp)
347155192Srwatson	struct utmp *bp;
348155192Srwatson{
349155192Srwatson	ARG *step;
350155192Srwatson
351156889Srwatson	if (!arglist)
352155192Srwatson		return (YES);
353155192Srwatson
354155192Srwatson	for (step = arglist; step; step = step->next)
355155192Srwatson		switch(step->type) {
356155192Srwatson		case HOST_TYPE:
357155192Srwatson			if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE))
358155192Srwatson				return (YES);
359170196Srwatson			break;
360170196Srwatson		case TTY_TYPE:
361170196Srwatson			if (!strncmp(step->name, bp->ut_line, UT_LINESIZE))
362170196Srwatson				return (YES);
363155192Srwatson			break;
364155192Srwatson		case USER_TYPE:
365155192Srwatson			if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE))
366155192Srwatson				return (YES);
367155192Srwatson			break;
368155192Srwatson	}
369155192Srwatson	return (NO);
370155192Srwatson}
371155192Srwatson
372155192Srwatson/*
373155192Srwatson * addarg --
374170585Srwatson *	add an entry to a linked list of arguments
375155192Srwatson */
376155192Srwatsonvoid
377155192Srwatsonaddarg(type, arg)
378155192Srwatson	int type;
379159269Srwatson	char *arg;
380159269Srwatson{
381159269Srwatson	ARG *cur;
382155192Srwatson
383159269Srwatson	if (!(cur = (ARG *)malloc((u_int)sizeof(ARG))))
384159269Srwatson		err(1, "malloc failure");
385159269Srwatson	cur->next = arglist;
386159269Srwatson	cur->type = type;
387159269Srwatson	cur->name = arg;
388159269Srwatson	arglist = cur;
389162380Scsjp}
390162380Scsjp
391155192Srwatson/*
392155192Srwatson * hostconv --
393155192Srwatson *	convert the hostname to search pattern; if the supplied host name
394159275Srwatson *	has a domain attached that is the same as the current domain, rip
395155192Srwatson *	off the domain suffix since that's what login(1) does.
396155192Srwatson */
397155192Srwatsonvoid
398155192Srwatsonhostconv(arg)
399155192Srwatson	char *arg;
400155192Srwatson{
401155192Srwatson	static int first = 1;
402155192Srwatson	static char *hostdot, name[MAXHOSTNAMELEN];
403155192Srwatson	char *argdot;
404155192Srwatson
405155192Srwatson	if (!(argdot = strchr(arg, '.')))
406170196Srwatson		return;
407155192Srwatson	if (first) {
408155192Srwatson		first = 0;
409155192Srwatson		if (gethostname(name, sizeof(name)))
410159275Srwatson			err(1, "gethostname");
411155192Srwatson		hostdot = strchr(name, '.');
412155192Srwatson	}
413156889Srwatson	if (hostdot && !strcasecmp(hostdot, argdot))
414155192Srwatson		*argdot = '\0';
415170182Srwatson}
416170182Srwatson
417155192Srwatson/*
418170182Srwatson * ttyconv --
419159261Srwatson *	convert tty to correct name.
420155192Srwatson */
421155192Srwatsonchar *
422155192Srwatsonttyconv(arg)
423155192Srwatson	char *arg;
424159261Srwatson{
425155192Srwatson	char *mval;
426155192Srwatson
427155192Srwatson	/*
428155192Srwatson	 * kludge -- we assume that all tty's end with
429155192Srwatson	 * a two character suffix.
430155192Srwatson	 */
431155192Srwatson	if (strlen(arg) == 2) {
432155192Srwatson		/* either 6 for "ttyxx" or 8 for "console" */
433155192Srwatson		if (!(mval = malloc((u_int)8)))
434155192Srwatson			err(1, "malloc failure");
435155192Srwatson		if (!strcmp(arg, "co"))
436155192Srwatson			(void)strcpy(mval, "console");
437155192Srwatson		else {
438159269Srwatson			(void)strcpy(mval, "tty");
439159269Srwatson			(void)strcpy(mval + 3, arg);
440159269Srwatson		}
441155192Srwatson		return (mval);
442155192Srwatson	}
443155192Srwatson	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
444155192Srwatson		return (arg + 5);
445155192Srwatson	return (arg);
446155192Srwatson}
447155192Srwatson
448155192Srwatson/*
449162950Srwatson * onintr --
450162950Srwatson *	on interrupt, we inform the user how far we've gotten
451155192Srwatson */
452155192Srwatsonvoid
453155192Srwatsononintr(signo)
454155192Srwatson	int signo;
455159269Srwatson{
456159269Srwatson	char ct[80];
457155192Srwatson	struct tm *tm;
458155192Srwatson
459155192Srwatson	tm = localtime(&buf[0].ut_time);
460155192Srwatson	(void) strftime(ct, sizeof(ct),
461155192Srwatson			d_first ? "%a %e %b %R" : "%a %b %e %R",
462155192Srwatson			tm);
463170407Srwatson	printf("\ninterrupted %s\n", ct);
464159269Srwatson	if (signo == SIGINT)
465155192Srwatson		exit(1);
466155192Srwatson	(void)fflush(stdout);			/* fix required for rsh */
467170407Srwatson}
468156889Srwatson