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