xref: /illumos-gate/usr/src/cmd/last/last.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  *	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
29  *	  All Rights Reserved
30  */
31 
32 /*
33  * University Copyright- Copyright (c) 1982, 1986, 1988
34  * The Regents of the University of California
35  * All Rights Reserved
36  *
37  * University Acknowledgment- Portions of this document are derived from
38  * software developed by the University of California, Berkeley, and its
39  * contributors.
40  */
41 
42 #pragma ident	"%Z%%M%	%I%	%E% SMI"
43 
44 /*
45  * last
46  */
47 #include <sys/types.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <unistd.h>
51 #include <strings.h>
52 #include <signal.h>
53 #include <sys/stat.h>
54 #include <pwd.h>
55 #include <fcntl.h>
56 #include <utmpx.h>
57 #include <locale.h>
58 #include <ctype.h>
59 
60 /*
61  * NMAX, LMAX and HMAX are set to these values for now. They
62  * should be much higher because of the max allowed limit in
63  * utmpx.h
64  */
65 #define	NMAX	8
66 #define	LMAX	12
67 #define	HMAX	(sizeof (((struct utmpx *)0)->ut_host))
68 #define	SECDAY	(24*60*60)
69 #define	CHUNK_SIZE 256
70 
71 #define	lineq(a, b)	(strncmp(a, b, LMAX) == 0)
72 #define	nameq(a, b)	(strncmp(a, b, NMAX) == 0)
73 #define	hosteq(a, b)	(strncmp(a, b, HMAX) == 0)
74 #define	linehostnameq(a, b, c, d) \
75 	    (lineq(a, b)&&hosteq(a+LMAX+1, c)&&nameq(a+LMAX+HMAX+2, d))
76 
77 #define	USAGE	"usage: last [-n number] [-f filename] [-a ] [name | tty] ...\n"
78 
79 /* Beware: These are set in main() to exclude the executable name.  */
80 static char	**argv;
81 static int	argc;
82 static char	**names;
83 static int	names_num;
84 
85 static struct	utmpx buf[128];
86 
87 /*
88  * ttnames and logouts are allocated in the blocks of
89  * CHUNK_SIZE lines whenever needed. The count of the
90  * current size is maintained in the variable "lines"
91  * The variable bootxtime is used to hold the time of
92  * the last BOOT_TIME
93  * All elements of the logouts are initialised to bootxtime
94  * everytime the buffer is reallocated.
95  */
96 
97 static char	**ttnames;
98 static time_t	*logouts;
99 static time_t	bootxtime;
100 static int	lines;
101 static char	timef[128];
102 static char	hostf[HMAX + 1];
103 
104 static char *strspl(char *, char *);
105 static void onintr(int);
106 static void reallocate_buffer();
107 static void memory_alloc(int);
108 static int want(struct utmpx *, char **, char **);
109 static void record_time(time_t *, int *, int, struct utmpx *);
110 
111 int
112 main(int ac, char **av)
113 {
114 	int i, j;
115 	int aflag = 0;
116 	int fpos;	/* current position in time format buffer */
117 	int chrcnt;	/* # of chars formatted by current sprintf */
118 	int bl, wtmp;
119 	char *ct;
120 	char *ut_host;
121 	char *ut_user;
122 	struct utmpx *bp;
123 	time_t otime;
124 	struct stat stb;
125 	int print = 0;
126 	char *crmsg = (char *)0;
127 	long outrec = 0;
128 	long maxrec = 0x7fffffffL;
129 	char *wtmpfile = "/var/adm/wtmpx";
130 	size_t hostf_len;
131 
132 	(void) setlocale(LC_ALL, "");
133 #if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D */
134 #define	TEXT_DOMAIN "SYS_TEST"		/* Use this only if it weren't. */
135 #endif
136 	(void) textdomain(TEXT_DOMAIN);
137 
138 	(void) time(&buf[0].ut_xtime);
139 	ac--, av++;
140 	argc = ac;
141 	argv = av;
142 	names = malloc(argc * sizeof (char *));
143 	if (names == NULL) {
144 		perror("last");
145 		exit(2);
146 	}
147 	names_num = 0;
148 	for (i = 0; i < argc; i++) {
149 		if (argv[i][0] == '-') {
150 
151 			/* -[0-9]*   sets max # records to print */
152 			if (isdigit(argv[i][1])) {
153 				maxrec = atoi(argv[i]+1);
154 				continue;
155 			}
156 
157 			for (j = 1; argv[i][j] != '\0'; ++j) {
158 				switch (argv[i][j]) {
159 
160 				/* -f name sets filename of wtmp file */
161 				case 'f':
162 					if (argv[i][j+1] != '\0') {
163 						wtmpfile = &argv[i][j+1];
164 					} else if (i+1 < argc) {
165 						wtmpfile = argv[++i];
166 					} else {
167 						(void) fprintf(stderr,
168 						    gettext("last: argument to "
169 						    "-f is missing\n"));
170 						(void) fprintf(stderr,
171 						    gettext(USAGE));
172 						exit(1);
173 					}
174 					goto next_word;
175 
176 				/* -n number sets max # records to print */
177 				case 'n': {
178 					char *arg;
179 
180 					if (argv[i][j+1] != '\0') {
181 						arg = &argv[i][j+1];
182 					} else if (i+1 < argc) {
183 						arg = argv[++i];
184 					} else {
185 						(void) fprintf(stderr,
186 						    gettext("last: argument to "
187 						    "-n is missing\n"));
188 						(void) fprintf(stderr,
189 						    gettext(USAGE));
190 						exit(1);
191 					}
192 
193 					if (!isdigit(*arg)) {
194 						(void) fprintf(stderr,
195 						    gettext("last: argument to "
196 						    "-n is not a number\n"));
197 						(void) fprintf(stderr,
198 						    gettext(USAGE));
199 						exit(1);
200 					}
201 					maxrec = atoi(arg);
202 					goto next_word;
203 				}
204 
205 				/* -a displays hostname last on the line */
206 				case 'a':
207 					aflag++;
208 					break;
209 
210 				default:
211 					(void) fprintf(stderr, gettext(USAGE));
212 					exit(1);
213 				}
214 			}
215 
216 next_word:
217 			continue;
218 		}
219 
220 		if (strlen(argv[i]) > 2 || strcmp(argv[i], "~") == 0 ||
221 		    getpwnam(argv[i]) != NULL) {
222 			/* Not a tty number. */
223 			names[names_num] = argv[i];
224 			++names_num;
225 		} else {
226 			/* tty number.  Prepend "tty". */
227 			names[names_num] = strspl("tty", argv[i]);
228 			++names_num;
229 		}
230 	}
231 
232 	wtmp = open(wtmpfile, 0);
233 	if (wtmp < 0) {
234 		perror(wtmpfile);
235 		exit(1);
236 	}
237 	(void) fstat(wtmp, &stb);
238 	bl = (stb.st_size + sizeof (buf)-1) / sizeof (buf);
239 	if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
240 		(void) signal(SIGINT, onintr);
241 		(void) signal(SIGQUIT, onintr);
242 	}
243 	lines = CHUNK_SIZE;
244 	ttnames = calloc(lines, sizeof (char *));
245 	logouts = calloc(lines, sizeof (time_t));
246 	if (ttnames == NULL || logouts == NULL) {
247 		(void) fprintf(stderr, gettext("Out of memory \n "));
248 		exit(2);
249 	}
250 		for (bl--; bl >= 0; bl--) {
251 		(void) lseek(wtmp, (off_t)(bl * sizeof (buf)), 0);
252 		bp = &buf[read(wtmp, buf, sizeof (buf)) / sizeof (buf[0]) - 1];
253 		for (; bp >= buf; bp--) {
254 			if (want(bp, &ut_host, &ut_user)) {
255 				for (i = 0; i <= lines; i++) {
256 				if (i == lines)
257 				    reallocate_buffer();
258 				if (ttnames[i] == NULL) {
259 				    memory_alloc(i);
260 					/*
261 					 * LMAX+HMAX+NMAX+3 bytes have been
262 					 * allocated for ttnames[i].
263 					 * If bp->ut_line is longer than LMAX,
264 					 * ut_host is longer than HMAX,
265 					 * and ut_user is longer than NMAX,
266 					 * truncate it to fit ttnames[i].
267 					 */
268 					(void) strlcpy(ttnames[i], bp->ut_line,
269 						LMAX+1);
270 					(void) strlcpy(ttnames[i]+LMAX+1,
271 						ut_host, HMAX+1);
272 					(void) strlcpy(ttnames[i]+LMAX+HMAX+2,
273 						ut_user, NMAX+1);
274 						record_time(&otime, &print,
275 							i, bp);
276 						break;
277 					} else if (linehostnameq(ttnames[i],
278 					    bp->ut_line, ut_host, ut_user)) {
279 						record_time(&otime,
280 						    &print, i, bp);
281 						break;
282 					}
283 				}
284 			}
285 			if (print) {
286 				if (strncmp(bp->ut_line, "ftp", 3) == 0)
287 					bp->ut_line[3] = '\0';
288 				if (strncmp(bp->ut_line, "uucp", 4) == 0)
289 					bp->ut_line[4] = '\0';
290 
291 				ct = ctime(&bp->ut_xtime);
292 				(void) printf(gettext("%-*.*s  %-*.*s "),
293 				    NMAX, NMAX, bp->ut_name,
294 				    LMAX, LMAX, bp->ut_line);
295 				hostf_len = strlen(bp->ut_host);
296 				(void) snprintf(hostf, sizeof (hostf),
297 				    "%-*.*s", hostf_len, hostf_len,
298 				    bp->ut_host);
299 				fpos = snprintf(timef, sizeof (timef),
300 					"%10.10s %5.5s ",
301 				    ct, 11 + ct);
302 				if (!lineq(bp->ut_line, "system boot") &&
303 				    !lineq(bp->ut_line, "system down")) {
304 					if (otime == 0 &&
305 					    bp->ut_type == USER_PROCESS) {
306 
307 	if (fpos < sizeof (timef)) {
308 		/* timef still has room */
309 		(void) snprintf(timef + fpos, sizeof (timef) - fpos,
310 			gettext("  still logged in"));
311 	}
312 
313 					} else {
314 					time_t delta;
315 					if (otime < 0) {
316 						otime = -otime;
317 						/*
318 						 * TRANSLATION_NOTE
319 						 * See other notes on "down"
320 						 * and "- %5.5s".
321 						 * "-" means "until".  This
322 						 * is displayed after the
323 						 * starting time as in:
324 						 * 	16:20 - down
325 						 * You probably don't want to
326 						 * translate this.  Should you
327 						 * decide to translate this,
328 						 * translate "- %5.5s" too.
329 						 */
330 
331 	if (fpos < sizeof (timef)) {
332 		/* timef still has room */
333 		chrcnt = snprintf(timef + fpos, sizeof (timef) - fpos,
334 			gettext("- %s"), crmsg);
335 		fpos += chrcnt;
336 	}
337 
338 					} else {
339 
340 	if (fpos < sizeof (timef)) {
341 		/* timef still has room */
342 		chrcnt = snprintf(timef + fpos, sizeof (timef) - fpos,
343 			gettext("- %5.5s"), ctime(&otime) + 11);
344 		fpos += chrcnt;
345 	}
346 
347 					}
348 					delta = otime - bp->ut_xtime;
349 					if (delta < SECDAY) {
350 
351 	if (fpos < sizeof (timef)) {
352 		/* timef still has room */
353 		(void) snprintf(timef + fpos, sizeof (timef) - fpos,
354 			gettext("  (%5.5s)"), asctime(gmtime(&delta)) + 11);
355 	}
356 
357 					} else {
358 
359 	if (fpos < sizeof (timef)) {
360 		/* timef still has room */
361 		(void) snprintf(timef + fpos, sizeof (timef) - fpos,
362 			gettext(" (%ld+%5.5s)"), delta / SECDAY,
363 		    asctime(gmtime(&delta)) + 11);
364 	}
365 
366 					}
367 				}
368 				}
369 				if (aflag)
370 					(void) printf("%-35.35s %-.*s\n",
371 					    timef, strlen(hostf), hostf);
372 				else
373 					(void) printf("%-16.16s %-.35s\n",
374 					    hostf, timef);
375 				(void) fflush(stdout);
376 				if (++outrec >= maxrec)
377 					exit(0);
378 			}
379 			/*
380 			 * when the system is down or crashed.
381 			 */
382 			if (bp->ut_type == BOOT_TIME) {
383 				for (i = 0; i < lines; i++)
384 					logouts[i] = -bp->ut_xtime;
385 				bootxtime = -bp->ut_xtime;
386 				/*
387 				 * TRANSLATION_NOTE
388 				 * Translation of this "down " will replace
389 				 * the %s in "- %s".  "down" is used instead
390 				 * of the real time session was ended, probably
391 				 * because the session ended by a sudden crash.
392 				 */
393 				crmsg = gettext("down ");
394 			}
395 			print = 0;	/* reset the print flag */
396 		}
397 	}
398 	ct = ctime(&buf[0].ut_xtime);
399 	(void) printf(gettext("\nwtmp begins %10.10s %5.5s \n"), ct, ct + 11);
400 
401 	/* free() called to prevent lint warning about names */
402 	free(names);
403 
404 	return (0);
405 }
406 
407 static void
408 reallocate_buffer()
409 {
410 	int j;
411 	static char	**tmpttnames;
412 	static time_t	*tmplogouts;
413 
414 	lines += CHUNK_SIZE;
415 	tmpttnames = realloc(ttnames, sizeof (char *)*lines);
416 	tmplogouts = realloc(logouts, sizeof (time_t)*lines);
417 	if (tmpttnames == NULL || tmplogouts == NULL) {
418 		(void) fprintf(stderr, gettext("Out of memory \n"));
419 		exit(2);
420 	} else {
421 	    ttnames = tmpttnames;
422 	    logouts = tmplogouts;
423 	}
424 	for (j = lines-CHUNK_SIZE; j < lines; j++) {
425 		ttnames[j] = NULL;
426 		logouts[j] = bootxtime;
427 	}
428 }
429 
430 static void
431 memory_alloc(int i)
432 {
433 	ttnames[i] = (char *)malloc(LMAX + HMAX + NMAX + 3);
434 	if (ttnames[i] == NULL) {
435 		(void) fprintf(stderr, gettext("Out of memory \n "));
436 		exit(2);
437 	}
438 }
439 
440 static void
441 onintr(int signo)
442 {
443 	char *ct;
444 
445 	if (signo == SIGQUIT)
446 		(void) signal(SIGQUIT, (void(*)())onintr);
447 	ct = ctime(&buf[0].ut_xtime);
448 	(void) printf(gettext("\ninterrupted %10.10s %5.5s \n"), ct, ct + 11);
449 	(void) fflush(stdout);
450 	if (signo == SIGINT)
451 		exit(1);
452 }
453 
454 static int
455 want(struct utmpx *bp, char **host, char **user)
456 {
457 	char **name;
458 	int i;
459 	char *zerostr = "\0";
460 
461 	*host = zerostr; *user = zerostr;
462 
463 		/* if ut_line = dtremote for the users who did dtremote login */
464 	if (strncmp(bp->ut_line, "dtremote", 8) == 0) {
465 		*host = bp->ut_host;
466 		*user = bp->ut_user;
467 	}
468 		/* if ut_line = dtlocal for the users who did a dtlocal login */
469 	else if (strncmp(bp->ut_line, "dtlocal", 7) == 0) {
470 		*host = bp->ut_host;
471 		*user = bp->ut_user;
472 	}
473 		/*
474 		 * Both dtremote and dtlocal can have multiple entries in
475 		 * /var/adm/wtmpx with these values, so the user and host
476 		 * entries are also checked
477 		 */
478 	if ((bp->ut_type == BOOT_TIME) || (bp->ut_type == DOWN_TIME))
479 		(void) strcpy(bp->ut_user, "reboot");
480 
481 	if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS &&
482 	    bp->ut_type != BOOT_TIME && bp->ut_type != DOWN_TIME)
483 		return (0);
484 
485 	if (bp->ut_user[0] == '.')
486 		return (0);
487 
488 	if (names_num == 0) {
489 		if (bp->ut_line[0] != '\0')
490 			return (1);
491 	} else {
492 		name = names;
493 		for (i = 0; i < names_num; i++, name++) {
494 			if (nameq(*name, bp->ut_name) ||
495 			    lineq(*name, bp->ut_line) ||
496 			    (lineq(*name, "ftp") &&
497 			    (strncmp(bp->ut_line, "ftp", 3) == 0))) {
498 				return (1);
499 			}
500 		}
501 	}
502 	return (0);
503 }
504 
505 static char *
506 strspl(char *left, char *right)
507 {
508 	size_t ressize = strlen(left) + strlen(right) + 1;
509 
510 	char *res = malloc(ressize);
511 
512 	if (res == NULL) {
513 		perror("last");
514 		exit(2);
515 	}
516 	(void) strlcpy(res, left, ressize);
517 	(void) strlcat(res, right, ressize);
518 	return (res);
519 }
520 
521 static void
522 record_time(time_t *otime, int *print, int i, struct utmpx *bp)
523 {
524 	*otime = logouts[i];
525 	logouts[i] = bp->ut_xtime;
526 	if ((bp->ut_type == USER_PROCESS && bp->ut_user[0] != '\0') ||
527 	    (bp->ut_type == BOOT_TIME) || (bp->ut_type == DOWN_TIME))
528 		*print = 1;
529 	else
530 		*print = 0;
531 }
532