last.c revision 202197
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
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1987, 1993, 1994\n\
37	The Regents of the University of California.  All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41static const char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
42#endif /* not lint */
43#include <sys/cdefs.h>
44__FBSDID("$FreeBSD: head/usr.bin/last/last.c 202197 2010-01-13 18:06:31Z ed $");
45
46#include <sys/param.h>
47#include <sys/stat.h>
48
49#include <err.h>
50#include <errno.h>
51#include <fcntl.h>
52#include <langinfo.h>
53#include <locale.h>
54#include <paths.h>
55#include <signal.h>
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59#include <time.h>
60#include <timeconv.h>
61#include <unistd.h>
62#include <utmpx.h>
63#include <sys/queue.h>
64
65#define	NO	0				/* false/no */
66#define	YES	1				/* true/yes */
67#define	ATOI2(ar)	((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
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(idlisthead, idtab) idlist;
80
81struct idtab {
82	time_t	logout;				/* log out time */
83	char	id[sizeof ((struct utmpx *)0)->ut_id]; /* identifier */
84	LIST_ENTRY(idtab) list;
85};
86
87static const	char *crmsg;			/* cause of last reboot */
88static time_t	currentout;			/* current logout value */
89static long	maxrec;				/* records to display */
90static const	char *file = NULL;		/* wtmp file */
91static int	sflag = 0;			/* show delta in seconds */
92static int	width = 5;			/* show seconds in delta */
93static int	yflag;				/* show year */
94static int      d_first;
95static int	snapfound = 0;			/* found snapshot entry? */
96static time_t	snaptime;			/* if != 0, we will only
97						 * report users logged in
98						 * at this snapshot time
99						 */
100
101void	 addarg(int, char *);
102time_t	 dateconv(char *);
103void	 doentry(struct utmpx *);
104void	 hostconv(char *);
105void	 printentry(struct utmpx *, struct idtab *);
106char	*ttyconv(char *);
107int	 want(struct utmpx *);
108void	 usage(void);
109void	 wtmp(void);
110
111void
112usage(void)
113{
114	(void)fprintf(stderr,
115"usage: last [-swy] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n"
116"            [-n maxrec] [-t tty] [user ...]\n");
117	exit(1);
118}
119
120int
121main(int argc, char *argv[])
122{
123	int ch;
124	char *p;
125
126	(void) setlocale(LC_TIME, "");
127	d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
128
129	maxrec = -1;
130	snaptime = 0;
131	while ((ch = getopt(argc, argv, "0123456789d:f:h:n:st:wy")) != -1)
132		switch (ch) {
133		case '0': case '1': case '2': case '3': case '4':
134		case '5': case '6': case '7': case '8': case '9':
135			/*
136			 * kludge: last was originally designed to take
137			 * a number after a dash.
138			 */
139			if (maxrec == -1) {
140				p = strchr(argv[optind - 1], ch);
141				if (p == NULL)
142					p = strchr(argv[optind], ch);
143				maxrec = atol(p);
144				if (!maxrec)
145					exit(0);
146			}
147			break;
148		case 'd':
149			snaptime = dateconv(optarg);
150			break;
151		case 'f':
152			file = optarg;
153			break;
154		case 'h':
155			hostconv(optarg);
156			addarg(HOST_TYPE, optarg);
157			break;
158		case 'n':
159			errno = 0;
160			maxrec = strtol(optarg, &p, 10);
161			if (p == optarg || *p != '\0' || errno != 0 ||
162			    maxrec <= 0)
163				errx(1, "%s: bad line count", optarg);
164			break;
165		case 's':
166			sflag++;	/* Show delta as seconds */
167			break;
168		case 't':
169			addarg(TTY_TYPE, ttyconv(optarg));
170			break;
171		case 'w':
172			width = 8;
173			break;
174		case 'y':
175			yflag++;
176			break;
177		case '?':
178		default:
179			usage();
180		}
181
182	if (sflag && width == 8) usage();
183
184	if (argc) {
185		setlinebuf(stdout);
186		for (argv += optind; *argv; ++argv) {
187#define	COMPATIBILITY
188#ifdef	COMPATIBILITY
189			/* code to allow "last p5" to work */
190			addarg(TTY_TYPE, ttyconv(*argv));
191#endif
192			addarg(USER_TYPE, *argv);
193		}
194	}
195	wtmp();
196	exit(0);
197}
198
199#define	MAXUTXENTRIES	1024
200
201/*
202 * wtmp --
203 *	read through the wtmp file
204 */
205void
206wtmp(void)
207{
208	struct utmpx buf[MAXUTXENTRIES];
209	struct utmpx *ut;
210	static unsigned int first = 0, amount = 0;
211	time_t t;
212	char ct[80];
213	struct tm *tm;
214
215	LIST_INIT(&idlist);
216	(void)time(&t);
217
218	/* Load the last entries from the file. */
219	if (setutxdb(UTXDB_LOG, file) != 0)
220		err(1, "%s", file);
221	while ((ut = getutxent()) != NULL) {
222		memcpy(&buf[(first + amount) % MAXUTXENTRIES], ut, sizeof *ut);
223		if (amount == MAXUTXENTRIES)
224			first++;
225		else
226			amount++;
227		if (t > ut->ut_tv.tv_sec)
228			t = ut->ut_tv.tv_sec;
229	}
230	endutxent();
231
232	/* Display them in reverse order. */
233	while (amount > 0)
234		doentry(&buf[(first + amount--) % MAXUTXENTRIES]);
235
236	tm = localtime(&t);
237	(void) strftime(ct, sizeof(ct), "\nwtmp begins %+\n", tm);
238	printf("%s", ct);
239}
240
241/*
242 * doentry --
243 *	process a single wtmp entry
244 */
245void
246doentry(struct utmpx *bp)
247{
248	struct idtab	*tt, *ttx;		/* idlist entry */
249
250	/* the machine stopped */
251	if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) {
252		/* everybody just logged out */
253		for (tt = LIST_FIRST(&idlist); tt;) {
254			LIST_REMOVE(tt, list);
255			ttx = tt;
256			tt = LIST_NEXT(tt, list);
257			free(ttx);
258		}
259		currentout = -bp->ut_tv.tv_sec;
260		crmsg = bp->ut_type != SHUTDOWN_TIME ?
261		    "crash" : "shutdown";
262		/*
263		 * if we're in snapshot mode, we want to exit if this
264		 * shutdown/reboot appears while we we are tracking the
265		 * active range
266		 */
267		if (snaptime && snapfound)
268			exit(0);
269		/*
270		 * don't print shutdown/reboot entries unless flagged for
271		 */
272		if (!snaptime && want(bp))
273			printentry(bp, NULL);
274		return;
275	}
276	/* date got set */
277	if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) {
278		if (want(bp) && !snaptime)
279			printentry(bp, NULL);
280		return;
281	}
282
283	if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS)
284		return;
285
286	/* find associated identifier */
287	LIST_FOREACH(tt, &idlist, list)
288	    if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id))
289		    break;
290
291	if (tt == NULL) {
292		/* add new one */
293		tt = malloc(sizeof(struct idtab));
294		if (tt == NULL)
295			errx(1, "malloc failure");
296		tt->logout = currentout;
297		memcpy(tt->id, bp->ut_id, sizeof bp->ut_id);
298		LIST_INSERT_HEAD(&idlist, tt, list);
299	}
300
301	/*
302	 * print record if not in snapshot mode and wanted
303	 * or in snapshot mode and in snapshot range
304	 */
305	if (bp->ut_type == USER_PROCESS && (want(bp) ||
306	    (bp->ut_tv.tv_sec < snaptime &&
307	    (tt->logout > snaptime || tt->logout < 1)))) {
308		snapfound = 1;
309		printentry(bp, tt);
310	}
311	tt->logout = bp->ut_tv.tv_sec;
312}
313
314/*
315 * printentry --
316 *	output an entry
317 *
318 * If `tt' is non-NULL, use it and `crmsg' to print the logout time or
319 * logout type (crash/shutdown) as appropriate.
320 */
321void
322printentry(struct utmpx *bp, struct idtab *tt)
323{
324	char ct[80];
325	struct tm *tm;
326	time_t	delta;				/* time difference */
327	time_t	t;
328
329	if (maxrec != -1 && !maxrec--)
330		exit(0);
331	t = bp->ut_tv.tv_sec;
332	tm = localtime(&t);
333	(void) strftime(ct, sizeof(ct), d_first ?
334	    (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") :
335	    (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm);
336	switch (bp->ut_type) {
337	case BOOT_TIME:
338		printf("%-42s", "boot time");
339		break;
340	case SHUTDOWN_TIME:
341		printf("%-42s", "shutdown time");
342		break;
343	case OLD_TIME:
344		printf("%-42s", "old time");
345		break;
346	case NEW_TIME:
347		printf("%-42s", "new time");
348		break;
349	case USER_PROCESS:
350		printf("%-10s %-8s %-22.22s",
351		    bp->ut_user, bp->ut_line, bp->ut_host);
352		break;
353	}
354	printf(" %s%c", ct, tt == NULL ? '\n' : ' ');
355	if (tt == NULL)
356		return;
357	if (!tt->logout) {
358		puts("  still logged in");
359		return;
360	}
361	if (tt->logout < 0) {
362		tt->logout = -tt->logout;
363		printf("- %s", crmsg);
364	} else {
365		tm = localtime(&tt->logout);
366		(void) strftime(ct, sizeof(ct), "%R", tm);
367		printf("- %s", ct);
368	}
369	delta = tt->logout - bp->ut_tv.tv_sec;
370	if (sflag) {
371		printf("  (%8ld)\n", (long)delta);
372	} else {
373		tm = gmtime(&delta);
374		(void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm);
375		if (delta < 86400)
376			printf("  (%s)\n", ct);
377		else
378			printf(" (%ld+%s)\n", (long)delta / 86400, ct);
379	}
380}
381
382/*
383 * want --
384 *	see if want this entry
385 */
386int
387want(struct utmpx *bp)
388{
389	ARG *step;
390
391	if (snaptime)
392		return (NO);
393
394	if (!arglist)
395		return (YES);
396
397	for (step = arglist; step; step = step->next)
398		switch(step->type) {
399		case HOST_TYPE:
400			if (!strcasecmp(step->name, bp->ut_host))
401				return (YES);
402			break;
403		case TTY_TYPE:
404			if (!strcmp(step->name, bp->ut_line))
405				return (YES);
406			break;
407		case USER_TYPE:
408			if (!strcmp(step->name, bp->ut_user))
409				return (YES);
410			break;
411		}
412	return (NO);
413}
414
415/*
416 * addarg --
417 *	add an entry to a linked list of arguments
418 */
419void
420addarg(int type, char *arg)
421{
422	ARG *cur;
423
424	if ((cur = malloc(sizeof(ARG))) == NULL)
425		errx(1, "malloc failure");
426	cur->next = arglist;
427	cur->type = type;
428	cur->name = arg;
429	arglist = cur;
430}
431
432/*
433 * hostconv --
434 *	convert the hostname to search pattern; if the supplied host name
435 *	has a domain attached that is the same as the current domain, rip
436 *	off the domain suffix since that's what login(1) does.
437 */
438void
439hostconv(char *arg)
440{
441	static int first = 1;
442	static char *hostdot, name[MAXHOSTNAMELEN];
443	char *argdot;
444
445	if (!(argdot = strchr(arg, '.')))
446		return;
447	if (first) {
448		first = 0;
449		if (gethostname(name, sizeof(name)))
450			err(1, "gethostname");
451		hostdot = strchr(name, '.');
452	}
453	if (hostdot && !strcasecmp(hostdot, argdot))
454		*argdot = '\0';
455}
456
457/*
458 * ttyconv --
459 *	convert tty to correct name.
460 */
461char *
462ttyconv(char *arg)
463{
464	char *mval;
465
466	/*
467	 * kludge -- we assume that all tty's end with
468	 * a two character suffix.
469	 */
470	if (strlen(arg) == 2) {
471		/* either 6 for "ttyxx" or 8 for "console" */
472		if ((mval = malloc(8)) == NULL)
473			errx(1, "malloc failure");
474		if (!strcmp(arg, "co"))
475			(void)strcpy(mval, "console");
476		else {
477			(void)strcpy(mval, "tty");
478			(void)strcpy(mval + 3, arg);
479		}
480		return (mval);
481	}
482	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
483		return (arg + 5);
484	return (arg);
485}
486
487/*
488 * dateconv --
489 * 	Convert the snapshot time in command line given in the format
490 * 	[[CC]YY]MMDDhhmm[.SS]] to a time_t.
491 * 	Derived from atime_arg1() in usr.bin/touch/touch.c
492 */
493time_t
494dateconv(char *arg)
495{
496        time_t timet;
497        struct tm *t;
498        int yearset;
499        char *p;
500
501        /* Start with the current time. */
502        if (time(&timet) < 0)
503                err(1, "time");
504        if ((t = localtime(&timet)) == NULL)
505                err(1, "localtime");
506
507        /* [[CC]YY]MMDDhhmm[.SS] */
508        if ((p = strchr(arg, '.')) == NULL)
509                t->tm_sec = 0; 		/* Seconds defaults to 0. */
510        else {
511                if (strlen(p + 1) != 2)
512                        goto terr;
513                *p++ = '\0';
514                t->tm_sec = ATOI2(p);
515        }
516
517        yearset = 0;
518        switch (strlen(arg)) {
519        case 12:                	/* CCYYMMDDhhmm */
520                t->tm_year = ATOI2(arg);
521                t->tm_year *= 100;
522                yearset = 1;
523                /* FALLTHROUGH */
524        case 10:                	/* YYMMDDhhmm */
525                if (yearset) {
526                        yearset = ATOI2(arg);
527                        t->tm_year += yearset;
528                } else {
529                        yearset = ATOI2(arg);
530                        if (yearset < 69)
531                                t->tm_year = yearset + 2000;
532                        else
533                                t->tm_year = yearset + 1900;
534                }
535                t->tm_year -= 1900;     /* Convert to UNIX time. */
536                /* FALLTHROUGH */
537        case 8:				/* MMDDhhmm */
538                t->tm_mon = ATOI2(arg);
539                --t->tm_mon;    	/* Convert from 01-12 to 00-11 */
540                t->tm_mday = ATOI2(arg);
541                t->tm_hour = ATOI2(arg);
542                t->tm_min = ATOI2(arg);
543                break;
544        case 4:				/* hhmm */
545                t->tm_hour = ATOI2(arg);
546                t->tm_min = ATOI2(arg);
547                break;
548        default:
549                goto terr;
550        }
551        t->tm_isdst = -1;       	/* Figure out DST. */
552        timet = mktime(t);
553        if (timet == -1)
554terr:           errx(1,
555        "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
556        return timet;
557}
558