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