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