last.c revision 81161
1/*
2 * Copyright (c) 1987, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 *
33 * $FreeBSD: head/usr.bin/last/last.c 81161 2001-08-05 16:01:34Z dd $
34 */
35
36#ifndef lint
37static const char copyright[] =
38"@(#) Copyright (c) 1987, 1993, 1994\n\
39	The Regents of the University of California.  All rights reserved.\n";
40#endif /* not lint */
41
42#ifndef lint
43static const char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
44#endif /* not lint */
45
46#include <sys/param.h>
47#include <sys/stat.h>
48
49#include <err.h>
50#include <fcntl.h>
51#include <langinfo.h>
52#include <locale.h>
53#include <paths.h>
54#include <signal.h>
55#include <stdio.h>
56#include <stdlib.h>
57#include <string.h>
58#include <time.h>
59#include <unistd.h>
60#include <utmp.h>
61#include <sys/queue.h>
62
63#define	NO	0				/* false/no */
64#define	YES	1				/* true/yes */
65#define	ATOI2(ar)	((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
66
67static struct utmp	buf[1024];		/* utmp read buffer */
68
69typedef struct arg {
70	char	*name;				/* argument */
71#define	HOST_TYPE	-2
72#define	TTY_TYPE	-3
73#define	USER_TYPE	-4
74	int	type;				/* type of arg */
75	struct arg	*next;			/* linked list pointer */
76} ARG;
77ARG	*arglist;				/* head of linked list */
78
79LIST_HEAD(ttylisthead, ttytab) ttylist;
80
81struct ttytab {
82	time_t	logout;				/* log out time */
83	char	tty[UT_LINESIZE + 1];		/* terminal name */
84	LIST_ENTRY(ttytab) list;
85};
86
87static long	currentout,			/* current logout value */
88		maxrec;				/* records to display */
89static const	char *file = _PATH_WTMP;		/* wtmp file */
90static int	sflag = 0;			/* show delta in seconds */
91static int	width = 5;			/* show seconds in delta */
92static int      d_first;
93static time_t	snaptime;			/* if != 0, we will only
94						 * report users logged in
95						 * at this snapshot time
96						 */
97
98void	 addarg __P((int, char *));
99time_t	 dateconv __P((char *));
100void	 hostconv __P((char *));
101void	 onintr __P((int));
102char	*ttyconv __P((char *));
103int	 want __P((struct utmp *));
104void	 usage __P((void));
105void	 wtmp __P((void));
106
107void
108usage(void)
109{
110	(void)fprintf(stderr,
111"usage: last [-#] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h hostname]\n"
112"\t[-t tty] [-s|w] [user ...]\n");
113	exit(1);
114}
115
116int
117main(argc, argv)
118	int argc;
119	char *argv[];
120{
121	int ch;
122	char *p;
123
124	(void) setlocale(LC_TIME, "");
125	d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
126
127	maxrec = -1;
128	snaptime = 0;
129	while ((ch = getopt(argc, argv, "0123456789d:f:h:st:w")) != -1)
130		switch (ch) {
131		case '0': case '1': case '2': case '3': case '4':
132		case '5': case '6': case '7': case '8': case '9':
133			/*
134			 * kludge: last was originally designed to take
135			 * a number after a dash.
136			 */
137			if (maxrec == -1) {
138				p = argv[optind - 1];
139				if (p[0] == '-' && p[1] == ch && !p[2])
140					maxrec = atol(++p);
141				else
142					maxrec = atol(argv[optind] + 1);
143				if (!maxrec)
144					exit(0);
145			}
146			break;
147		case 'd':
148			snaptime = dateconv(optarg);
149			break;
150		case 'f':
151			file = optarg;
152			break;
153		case 'h':
154			hostconv(optarg);
155			addarg(HOST_TYPE, optarg);
156			break;
157		case 's':
158			sflag++;	/* Show delta as seconds */
159			break;
160		case 't':
161			addarg(TTY_TYPE, ttyconv(optarg));
162			break;
163		case 'w':
164			width = 8;
165			break;
166		case '?':
167		default:
168			usage();
169		}
170
171	if (sflag && width == 8) usage();
172
173	if (argc) {
174		setlinebuf(stdout);
175		for (argv += optind; *argv; ++argv) {
176#define	COMPATIBILITY
177#ifdef	COMPATIBILITY
178			/* code to allow "last p5" to work */
179			addarg(TTY_TYPE, ttyconv(*argv));
180#endif
181			addarg(USER_TYPE, *argv);
182		}
183	}
184	wtmp();
185	exit(0);
186}
187
188/*
189 * wtmp --
190 *	read through the wtmp file
191 */
192void
193wtmp()
194{
195	struct utmp	*bp;			/* current structure */
196	struct ttytab	*tt, *ttx;		/* ttylist entry */
197	struct stat	stb;			/* stat of file for size */
198	long	bl;
199	time_t	delta;				/* time difference */
200	int	bytes, wfd;
201	const	char *crmsg;
202	char ct[80];
203	struct tm *tm;
204	int	 snapfound = 0;			/* found snapshot entry? */
205
206	LIST_INIT(&ttylist);
207
208	if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1)
209		err(1, "%s", file);
210	bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf);
211
212	(void)time(&buf[0].ut_time);
213	(void)signal(SIGINT, onintr);
214	(void)signal(SIGQUIT, onintr);
215
216	while (--bl >= 0) {
217		if (lseek(wfd, (off_t)(bl * sizeof(buf)), L_SET) == -1 ||
218		    (bytes = read(wfd, buf, sizeof(buf))) == -1)
219			err(1, "%s", file);
220		for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) {
221			/*
222			 * if the terminal line is '~', the machine stopped.
223			 * see utmp(5) for more info.
224			 */
225			if (bp->ut_line[0] == '~' && !bp->ut_line[1]) {
226				/* everybody just logged out */
227				for (tt = LIST_FIRST(&ttylist); tt;) {
228					LIST_REMOVE(tt, list);
229					ttx = tt;
230					tt = LIST_NEXT(tt, list);
231					free(ttx);
232				}
233				currentout = -bp->ut_time;
234				crmsg = strncmp(bp->ut_name, "shutdown",
235				    UT_NAMESIZE) ? "crash" : "shutdown";
236				/*
237				 * if we're in snapshot mode, we want to
238				 * exit if this shutdown/reboot appears
239				 * while we we are tracking the active
240				 * range
241				 */
242				if (snaptime && snapfound)
243					return;
244				/*
245				 * don't print shutdown/reboot entries
246				 * unless flagged for
247				 */
248				if (!snaptime && want(bp)) {
249					tm = localtime(&bp->ut_time);
250					(void) strftime(ct, sizeof(ct),
251						     d_first ? "%a %e %b %R" :
252							       "%a %b %e %R",
253						     tm);
254					printf("%-*.*s %-*.*s %-*.*s %s\n",
255					    UT_NAMESIZE, UT_NAMESIZE,
256					    bp->ut_name, UT_LINESIZE,
257					    UT_LINESIZE, bp->ut_line,
258					    UT_HOSTSIZE, UT_HOSTSIZE,
259					    bp->ut_host, ct);
260					if (maxrec != -1 && !--maxrec)
261						return;
262				}
263				continue;
264			}
265			/*
266			 * if the line is '{' or '|', date got set; see
267			 * utmp(5) for more info.
268			 */
269			if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|')
270			    && !bp->ut_line[1]) {
271				if (want(bp) && !snaptime) {
272					tm = localtime(&bp->ut_time);
273					(void) strftime(ct, sizeof(ct),
274						     d_first ? "%a %e %b %R" :
275							       "%a %b %e %R",
276						     tm);
277					printf("%-*.*s %-*.*s %-*.*s %s\n",
278					    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
279					    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
280					    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
281					    ct);
282					if (maxrec && !--maxrec)
283						return;
284				}
285				continue;
286			}
287			/* find associated tty */
288			LIST_FOREACH(tt, &ttylist, list)
289			    if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE))
290				    break;
291
292			if (tt == NULL) {
293				/* add new one */
294				tt = malloc(sizeof(struct ttytab));
295				if (tt == NULL)
296					err(1, "malloc failure");
297				tt->logout = currentout;
298				strncpy(tt->tty, bp->ut_line, UT_LINESIZE);
299				LIST_INSERT_HEAD(&ttylist, tt, list);
300			}
301
302			/*
303			 * print record if not in snapshot mode and wanted
304			 * or in snapshot mode and in snapshot range
305			 */
306			if (bp->ut_name[0] && (want(bp) ||
307			    (bp->ut_time < snaptime &&
308				(tt->logout > snaptime || tt->logout < 1)))) {
309				snapfound = 1;
310				/*
311				 * when uucp and ftp log in over a network, the entry in
312				 * the utmp file is the name plus their process id.  See
313				 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
314				 */
315				if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
316					bp->ut_line[3] = '\0';
317				else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
318					bp->ut_line[4] = '\0';
319				tm = localtime(&bp->ut_time);
320				(void) strftime(ct, sizeof(ct),
321				    d_first ? "%a %e %b %R" :
322				    "%a %b %e %R",
323				    tm);
324				printf("%-*.*s %-*.*s %-*.*s %s ",
325				    UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
326				    UT_LINESIZE, UT_LINESIZE, bp->ut_line,
327				    UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
328				    ct);
329				if (!tt->logout)
330					puts("  still logged in");
331				else {
332					if (tt->logout < 0) {
333						tt->logout = -tt->logout;
334						printf("- %s", crmsg);
335					}
336					else {
337						tm = localtime(&tt->logout);
338						(void) strftime(ct, sizeof(ct), "%R", tm);
339						printf("- %s", ct);
340					}
341					delta = tt->logout - bp->ut_time;
342					if ( sflag ) {
343						printf("  (%8ld)\n",
344						    (long)delta);
345					} else {
346						tm = gmtime(&delta);
347						(void) strftime(ct, sizeof(ct),
348						    width >= 8 ? "%T" : "%R",
349						    tm);
350						if (delta < 86400)
351							printf("  (%s)\n", ct);
352						else
353							printf(" (%ld+%s)\n",
354							    (long)delta /
355							    86400, ct);
356					}
357				}
358				if (maxrec != -1 && !--maxrec)
359					return;
360			}
361			tt->logout = bp->ut_time;
362		}
363	}
364	tm = localtime(&buf[0].ut_time);
365	(void) strftime(ct, sizeof(ct), "\nwtmp begins %+\n", tm);
366	printf("%s", ct);
367}
368
369/*
370 * want --
371 *	see if want this entry
372 */
373int
374want(bp)
375	struct utmp *bp;
376{
377	ARG *step;
378
379	if (snaptime)
380		return (NO);
381
382	if (!arglist)
383		return (YES);
384
385	for (step = arglist; step; step = step->next)
386		switch(step->type) {
387		case HOST_TYPE:
388			if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE))
389				return (YES);
390			break;
391		case TTY_TYPE:
392			if (!strncmp(step->name, bp->ut_line, UT_LINESIZE))
393				return (YES);
394			break;
395		case USER_TYPE:
396			if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE))
397				return (YES);
398			break;
399	}
400	return (NO);
401}
402
403/*
404 * addarg --
405 *	add an entry to a linked list of arguments
406 */
407void
408addarg(type, arg)
409	int type;
410	char *arg;
411{
412	ARG *cur;
413
414	if (!(cur = (ARG *)malloc((u_int)sizeof(ARG))))
415		err(1, "malloc failure");
416	cur->next = arglist;
417	cur->type = type;
418	cur->name = arg;
419	arglist = cur;
420}
421
422/*
423 * hostconv --
424 *	convert the hostname to search pattern; if the supplied host name
425 *	has a domain attached that is the same as the current domain, rip
426 *	off the domain suffix since that's what login(1) does.
427 */
428void
429hostconv(arg)
430	char *arg;
431{
432	static int first = 1;
433	static char *hostdot, name[MAXHOSTNAMELEN];
434	char *argdot;
435
436	if (!(argdot = strchr(arg, '.')))
437		return;
438	if (first) {
439		first = 0;
440		if (gethostname(name, sizeof(name)))
441			err(1, "gethostname");
442		hostdot = strchr(name, '.');
443	}
444	if (hostdot && !strcasecmp(hostdot, argdot))
445		*argdot = '\0';
446}
447
448/*
449 * ttyconv --
450 *	convert tty to correct name.
451 */
452char *
453ttyconv(arg)
454	char *arg;
455{
456	char *mval;
457
458	/*
459	 * kludge -- we assume that all tty's end with
460	 * a two character suffix.
461	 */
462	if (strlen(arg) == 2) {
463		/* either 6 for "ttyxx" or 8 for "console" */
464		if (!(mval = malloc((u_int)8)))
465			err(1, "malloc failure");
466		if (!strcmp(arg, "co"))
467			(void)strcpy(mval, "console");
468		else {
469			(void)strcpy(mval, "tty");
470			(void)strcpy(mval + 3, arg);
471		}
472		return (mval);
473	}
474	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
475		return (arg + 5);
476	return (arg);
477}
478
479/*
480 * dateconv --
481 * 	Convert the snapshot time in command line given in the format
482 * 	[[CC]YY]MMDDhhmm[.SS]] to a time_t.
483 * 	Derived from atime_arg1() in usr.bin/touch/touch.c
484 */
485time_t
486dateconv(arg)
487        char *arg;
488{
489        time_t timet;
490        struct tm *t;
491        int yearset;
492        char *p;
493
494        /* Start with the current time. */
495        if (time(&timet) < 0)
496                err(1, "time");
497        if ((t = localtime(&timet)) == NULL)
498                err(1, "localtime");
499
500        /* [[CC]YY]MMDDhhmm[.SS] */
501        if ((p = strchr(arg, '.')) == NULL)
502                t->tm_sec = 0; 		/* Seconds defaults to 0. */
503        else {
504                if (strlen(p + 1) != 2)
505                        goto terr;
506                *p++ = '\0';
507                t->tm_sec = ATOI2(p);
508        }
509
510        yearset = 0;
511        switch (strlen(arg)) {
512        case 12:                	/* CCYYMMDDhhmm */
513                t->tm_year = ATOI2(arg);
514                t->tm_year *= 100;
515                yearset = 1;
516                /* FALLTHOUGH */
517        case 10:                	/* YYMMDDhhmm */
518                if (yearset) {
519                        yearset = ATOI2(arg);
520                        t->tm_year += yearset;
521                } else {
522                        yearset = ATOI2(arg);
523                        if (yearset < 69)
524                                t->tm_year = yearset + 2000;
525                        else
526                                t->tm_year = yearset + 1900;
527                }
528                t->tm_year -= 1900;     /* Convert to UNIX time. */
529                /* FALLTHROUGH */
530        case 8:				/* MMDDhhmm */
531                t->tm_mon = ATOI2(arg);
532                --t->tm_mon;    	/* Convert from 01-12 to 00-11 */
533                t->tm_mday = ATOI2(arg);
534                t->tm_hour = ATOI2(arg);
535                t->tm_min = ATOI2(arg);
536                break;
537        case 4:				/* hhmm */
538                t->tm_hour = ATOI2(arg);
539                t->tm_min = ATOI2(arg);
540                break;
541        default:
542                goto terr;
543        }
544        t->tm_isdst = -1;       	/* Figure out DST. */
545        timet = mktime(t);
546        if (timet == -1)
547terr:           errx(1,
548        "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
549        return timet;
550}
551
552
553/*
554 * onintr --
555 *	on interrupt, we inform the user how far we've gotten
556 */
557void
558onintr(signo)
559	int signo;
560{
561	char ct[80];
562	struct tm *tm;
563
564	tm = localtime(&buf[0].ut_time);
565	(void) strftime(ct, sizeof(ct),
566			d_first ? "%a %e %b %R" : "%a %b %e %R",
567			tm);
568	printf("\ninterrupted %s\n", ct);
569	if (signo == SIGINT)
570		exit(1);
571	(void)fflush(stdout);			/* fix required for rsh */
572}
573