xref: /dragonfly/usr.bin/last/last.c (revision f9993810)
1 /*	@(#)last.c	8.2 (Berkeley) 4/2/94 */
2 /*	$NetBSD: last.c,v 1.15 2000/06/30 06:19:58 simonb Exp $	*/
3 
4 /*
5  * Copyright (c) 1987, 1993, 1994
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/param.h>
34 #include <sys/stat.h>
35 
36 #include <err.h>
37 #include <fcntl.h>
38 #include <paths.h>
39 #include <signal.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <time.h>
44 #include <tzfile.h>
45 #include <unistd.h>
46 #include <utmpx.h>
47 
48 #ifndef UT_NAMESIZE
49 #define UT_NAMESIZE 8
50 #define UT_LINESIZE 8
51 #define UT_HOSTSIZE 16
52 #endif
53 #ifndef SIGNATURE
54 #define SIGNATURE -1
55 #endif
56 
57 
58 
59 #define	NO	0			/* false/no */
60 #define	YES	1			/* true/yes */
61 
62 #define	TBUFLEN	30			/* length of time string buffer */
63 #define	TFMT	"%a %b %d %R"		/* strftime format string */
64 #define	LTFMT	"%a %b %d %Y %T"	/* strftime long format string */
65 #define	TFMTS	"%R"			/* strftime format string - time only */
66 #define	LTFMTS	"%T"			/* strftime long format string - " */
67 
68 /* fmttime() flags */
69 #define	FULLTIME	0x1		/* show year, seconds */
70 #define	TIMEONLY	0x2		/* show time only, not date */
71 #define	GMT		0x4		/* show time at GMT, for offsets only */
72 
73 #define MAXUTMP		1024;
74 
75 typedef struct arg {
76 	char	*name;			/* argument */
77 #define	HOST_TYPE	-2
78 #define	TTY_TYPE	-3
79 #define	USER_TYPE	-4
80 	int	type;			/* type of arg */
81 	struct arg	*next;		/* linked list pointer */
82 } ARG;
83 static ARG	*arglist;		/* head of linked list */
84 
85 typedef struct ttytab {
86 	time_t	logout;			/* log out time */
87 	char	tty[128];		/* terminal name */
88 	struct ttytab	*next;		/* linked list pointer */
89 } TTY;
90 static TTY	*ttylist;		/* head of linked list */
91 
92 static struct utmpx *bufx;
93 static time_t	currentout;		/* current logout value */
94 static long	maxrec;			/* records to display */
95 static int	fulltime = 0;		/* Display seconds? */
96 
97 static void	 addarg(int, char *);
98 static TTY	*addtty(const char *);
99 static void	 hostconv(char *);
100 static char	*ttyconv(char *);
101 static void	 wtmpx(const char *, int, int, int);
102 static char	*fmttime(time_t, int);
103 static void	 usage(void);
104 static void	 onintrx(int);
105 static int	 wantx(struct utmpx *, int);
106 
107 static
108 void usage(void)
109 {
110 	fprintf(stderr, "Usage: %s [-#%s] [-T] [-f file]"
111 	    " [-h host] [-H hostsize] [-L linesize]\n"
112 	    "\t    [-N namesize] [-t tty] [user ...]\n", getprogname(),
113 #if 0 /* XXX NOTYET_SUPPORT_UTMPX??? */
114 	    "w"
115 #else
116 	    ""
117 #endif
118 	);
119 	exit(1);
120 }
121 
122 int
123 main(int argc, char *argv[])
124 {
125 	int ch;
126 	char *p;
127 	const char *file = NULL;
128 	int namesize = UT_NAMESIZE;
129 	int linesize = UT_LINESIZE;
130 	int hostsize = UT_HOSTSIZE;
131 
132 	maxrec = -1;
133 
134 	while ((ch = getopt(argc, argv, "0123456789f:h:H:L:N:t:T")) != -1)
135 		switch (ch) {
136 		case '0': case '1': case '2': case '3': case '4':
137 		case '5': case '6': case '7': case '8': case '9':
138 			/*
139 			 * kludge: last was originally designed to take
140 			 * a number after a dash.
141 			 */
142 			if (maxrec == -1) {
143 				p = argv[optind - 1];
144 				if (p[0] == '-' && p[1] == ch && !p[2])
145 					maxrec = atol(++p);
146 				else
147 					maxrec = atol(argv[optind] + 1);
148 				if (!maxrec)
149 					exit(0);
150 			}
151 			break;
152 		case 'f':
153 			file = optarg;
154 			break;
155 		case 'h':
156 			hostconv(optarg);
157 			addarg(HOST_TYPE, optarg);
158 			break;
159 		case 't':
160 			addarg(TTY_TYPE, ttyconv(optarg));
161 			break;
162 		case 'U':
163 			namesize = atoi(optarg);
164 			break;
165 		case 'L':
166 			linesize = atoi(optarg);
167 			break;
168 		case 'H':
169 			hostsize = atoi(optarg);
170 			break;
171 		case 'T':
172 			fulltime = 1;
173 			break;
174 		case '?':
175 		default:
176 			usage();
177 		}
178 
179 	if (argc) {
180 		setlinebuf(stdout);
181 		for (argv += optind; *argv; ++argv) {
182 #define	COMPATIBILITY
183 #ifdef	COMPATIBILITY
184 			/* code to allow "last p5" to work */
185 			addarg(TTY_TYPE, ttyconv(*argv));
186 #endif
187 			addarg(USER_TYPE, *argv);
188 		}
189 	}
190 	if (file == NULL) {
191 		if (access(_PATH_WTMPX, R_OK) == 0)
192 			file = _PATH_WTMPX;
193 		if (file == NULL)
194 			errx(1, "Cannot access `%s'", _PATH_WTMPX);
195 	}
196 	wtmpx(file, namesize, linesize, hostsize);
197 	exit(0);
198 }
199 
200 
201 /*
202  * addarg --
203  *	add an entry to a linked list of arguments
204  */
205 static void
206 addarg(int type, char *arg)
207 {
208 	ARG *cur;
209 
210 	if (!(cur = (ARG *)malloc((u_int)sizeof(ARG))))
211 		err(1, "malloc failure");
212 	cur->next = arglist;
213 	cur->type = type;
214 	cur->name = arg;
215 	arglist = cur;
216 }
217 
218 /*
219  * addtty --
220  *	add an entry to a linked list of ttys
221  */
222 static TTY *
223 addtty(const char *tty)
224 {
225 	TTY *cur;
226 
227 	if (!(cur = (TTY *)malloc((u_int)sizeof(TTY))))
228 		err(1, "malloc failure");
229 	cur->next = ttylist;
230 	cur->logout = currentout;
231 	memmove(cur->tty, tty, sizeof(cur->tty));
232 	return (ttylist = cur);
233 }
234 
235 /*
236  * hostconv --
237  *	convert the hostname to search pattern; if the supplied host name
238  *	has a domain attached that is the same as the current domain, rip
239  *	off the domain suffix since that's what login(1) does.
240  */
241 static void
242 hostconv(char *arg)
243 {
244 	static int first = 1;
245 	static char *hostdot, name[MAXHOSTNAMELEN + 1];
246 	char *argdot;
247 
248 	if (!(argdot = strchr(arg, '.')))
249 		return;
250 	if (first) {
251 		first = 0;
252 		if (gethostname(name, sizeof(name)))
253 			err(1, "gethostname");
254 		name[sizeof(name) - 1] = '\0';
255 		hostdot = strchr(name, '.');
256 	}
257 	if (hostdot && !strcasecmp(hostdot, argdot))
258 		*argdot = '\0';
259 }
260 
261 /*
262  * ttyconv --
263  *	convert tty to correct name.
264  */
265 static char *
266 ttyconv(char *arg)
267 {
268 	char *mval;
269 
270 	/*
271 	 * kludge -- we assume that all tty's end with
272 	 * a two character suffix.
273 	 */
274 	if (strlen(arg) == 2) {
275 		/* either 6 for "ttyxx" or 8 for "console" */
276 		if (!(mval = malloc((u_int)8)))
277 			err(1, "malloc failure");
278 		if (!strcmp(arg, "co"))
279 			strcpy(mval, "console");
280 		else {
281 			strcpy(mval, "tty");
282 			strcpy(mval + 3, arg);
283 		}
284 		return (mval);
285 	}
286 	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
287 		return (arg + 5);
288 	return (arg);
289 }
290 
291 /*
292  * fmttime --
293  *	return pointer to (static) formatted time string.
294  */
295 static char *
296 fmttime(time_t t, int flags)
297 {
298 	struct tm *tm;
299 	static char tbuf[TBUFLEN];
300 
301 	tm = (flags & GMT) ? gmtime(&t) : localtime(&t);
302 	if (tm == NULL) {
303 		strcpy(tbuf, "????");
304 		return tbuf;
305 	}
306 	strftime(tbuf, sizeof(tbuf),
307 	    (flags & TIMEONLY)
308 	     ? (flags & FULLTIME ? LTFMTS : TFMTS)
309 	     : (flags & FULLTIME ? LTFMT : TFMT),
310 	    tm);
311 	return (tbuf);
312 }
313 
314 /*
315  * wtmpx --
316  *	read through the wtmpx file
317  */
318 static void
319 wtmpx(const char *file, int namesz, int linesz, int hostsz)
320 {
321 	struct utmpx	*bp;		/* current structure */
322 	TTY	*T;			/* tty list entry */
323 	struct stat	stb;		/* stat of file for sz */
324 	time_t	delta;			/* time difference */
325 	off_t	bl;
326 	int	bytes, wfd;
327 	char	*ct;
328 	const char *crmsg;
329 	size_t  len = sizeof(*bufx) * MAXUTMP;
330 
331 	if ((bufx = malloc(len)) == NULL)
332 		err(1, "Cannot allocate utmpx buffer");
333 
334 	crmsg = NULL;
335 
336 	if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1)
337 		err(1, "%s", file);
338 	bl = (stb.st_size + len - 1) / len;
339 
340 	bufx[1].ut_xtime = time(NULL);
341 	(void)signal(SIGINT, onintrx);
342 	(void)signal(SIGQUIT, onintrx);
343 
344 	while (--bl >= 0) {
345 		if (lseek(wfd, bl * len, SEEK_SET) == -1 ||
346 		    (bytes = read(wfd, bufx, len)) == -1)
347 			err(1, "%s", file);
348 		for (bp = &bufx[bytes / sizeof(*bufx) - 1]; bp >= bufx; --bp) {
349 			/*
350 			 * if the terminal line is '~', the machine stopped.
351 			 * see utmpx(5) for more info.
352 			 */
353 			if (bp->ut_line[0] == '~' && !bp->ut_line[1]) {
354 				/* everybody just logged out */
355 				for (T = ttylist; T; T = T->next)
356 					T->logout = -bp->ut_xtime;
357 				currentout = -bp->ut_xtime;
358 #ifdef __DragonFly__	/* XXX swildner: this should not be needed afaict */
359 				if (!strncmp(bp->ut_name, "shutdown", namesz))
360 					crmsg = "shutdown";
361 				else if (!strncmp(bp->ut_name, "reboot", namesz))
362 					crmsg = "reboot";
363 				else
364 					crmsg = "crash";
365 #else
366 				crmsg = strncmp(bp->ut_name, "shutdown",
367 				    namesz) ? "crash" : "shutdown";
368 #endif
369 				if (wantx(bp, NO)) {
370 					ct = fmttime(bp->ut_xtime, fulltime);
371 					printf("%-*.*s  %-*.*s %-*.*s %s\n",
372 					    namesz, namesz, bp->ut_name,
373 					    linesz, linesz, bp->ut_line,
374 					    hostsz, hostsz, bp->ut_host, ct);
375 					if (maxrec != -1 && !--maxrec)
376 						return;
377 				}
378 				continue;
379 			}
380 			/*
381 			 * if the line is '{' or '|', date got set; see
382 			 * utmpx(5) for more info.
383 			 */
384 			if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|')
385 			    && !bp->ut_line[1]) {
386 				if (wantx(bp, NO)) {
387 					ct = fmttime(bp->ut_xtime, fulltime);
388 				printf("%-*.*s  %-*.*s %-*.*s %s\n",
389 				    namesz, namesz,
390 				    bp->ut_name,
391 				    linesz, linesz,
392 				    bp->ut_line,
393 				    hostsz, hostsz,
394 				    bp->ut_host,
395 				    ct);
396 					if (maxrec && !--maxrec)
397 						return;
398 				}
399 				continue;
400 			}
401 			/* find associated tty */
402 			for (T = ttylist;; T = T->next) {
403 				if (!T) {
404 					/* add new one */
405 					T = addtty(bp->ut_line);
406 					break;
407 				}
408 				if (!strncmp(T->tty, bp->ut_line, UTX_LINESIZE))
409 					break;
410 			}
411 			if (bp->ut_type == SIGNATURE)
412 				continue;
413 			if (bp->ut_name[0] && wantx(bp, YES)) {
414 				ct = fmttime(bp->ut_xtime, fulltime);
415 				printf("%-*.*s  %-*.*s %-*.*s %s ",
416 				    namesz, namesz, bp->ut_name,
417 				    linesz, linesz, bp->ut_line,
418 				    hostsz, hostsz, bp->ut_host,
419 				    ct);
420 				if (!T->logout)
421 					puts("  still logged in");
422 				else {
423 					if (T->logout < 0) {
424 						T->logout = -T->logout;
425 						printf("- %s", crmsg);
426 					}
427 					else
428 						printf("- %s",
429 						    fmttime(T->logout,
430 						    fulltime | TIMEONLY));
431 					delta = T->logout - bp->ut_xtime;
432 					if (delta < SECSPERDAY)
433 						printf("  (%s)\n",
434 						    fmttime(delta,
435 						    fulltime | TIMEONLY | GMT));
436 					else
437 						printf(" (%ld+%s)\n",
438 						    delta / SECSPERDAY,
439 						    fmttime(delta,
440 						    fulltime | TIMEONLY | GMT));
441 				}
442 				if (maxrec != -1 && !--maxrec)
443 					return;
444 			}
445 			T->logout = bp->ut_xtime;
446 		}
447 	}
448 	fulltime = 1;	/* show full time */
449 	crmsg = fmttime(bufx[1].ut_xtime, FULLTIME);
450 	if ((ct = strrchr(file, '/')) != NULL)
451 		ct++;
452 	printf("\n%s begins %s\n", ct ? ct : file, crmsg);
453 }
454 
455 /*
456  * wantx --
457  *	see if want this entry
458  */
459 static int
460 wantx(struct utmpx *bp, int check)
461 {
462 	ARG *step;
463 
464 	if (check) {
465 		/*
466 		 * when uucp and ftp log in over a network, the entry in
467 		 * the utmpx file is the name plus their process id.  See
468 		 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
469 		 */
470 		if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
471 			bp->ut_line[3] = '\0';
472 		else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
473 			bp->ut_line[4] = '\0';
474 	}
475 	if (!arglist)
476 		return (YES);
477 
478 	for (step = arglist; step; step = step->next)
479 		switch(step->type) {
480 		case HOST_TYPE:
481 			if (!strncasecmp(step->name, bp->ut_host, UTX_HOSTSIZE))
482 				return (YES);
483 			break;
484 		case TTY_TYPE:
485 			if (!strncmp(step->name, bp->ut_line, UTX_LINESIZE))
486 				return (YES);
487 			break;
488 		case USER_TYPE:
489 			if (!strncmp(step->name, bp->ut_name, UTX_USERSIZE))
490 				return (YES);
491 			break;
492 	}
493 	return (NO);
494 }
495 
496 /*
497  * onintrx --
498  *	on interrupt, we inform the user how far we've gotten
499  */
500 static void
501 onintrx(int signo)
502 {
503 
504 	printf("\ninterrupted %s\n", fmttime(bufx[1].ut_xtime,
505 	    FULLTIME));
506 	if (signo == SIGINT)
507 		exit(1);
508 	(void)fflush(stdout);		/* fix required for rsh */
509 }
510