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
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.
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]
23 * Copyright (c) 2017 Olaf Bohlen
25 * Copyright (c) 2013 Gary Mills
27 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
28 * Use is subject to license terms.
32 * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
37 * University Copyright- Copyright (c) 1982, 1986, 1988
38 * The Regents of the University of California
41 * University Acknowledgment- Portions of this document are derived from
42 * software developed by the University of California, Berkeley, and its
49 #include <sys/types.h>
63 * Use the full lengths from utmpx for NMAX, LMAX and HMAX .
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))
69 /* Print minimum field widths. */
73 #define SECDAY (24*60*60)
74 #define CHUNK_SIZE 256
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))
82 #define USAGE "usage: last [-n number] [-f filename] [-a ] [ -l ] [name |\
85 /* Beware: These are set in main() to exclude the executable name. */
91 static struct utmpx buf
[128];
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
99 * All elements of the logouts are initialised to bootxtime
100 * everytime the buffer is reallocated.
103 static char **ttnames
;
104 static time_t *logouts
;
105 static time_t bootxtime
;
107 static char timef
[128];
108 static char hostf
[HMAX
+ 1];
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
*);
118 main(int ac
, char **av
)
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 */
135 long maxrec
= 0x7fffffffL
;
136 char *wtmpfile
= "/var/adm/wtmpx";
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. */
143 (void) textdomain(TEXT_DOMAIN
);
145 (void) time(&buf
[0].ut_xtime
);
149 names
= malloc(argc
* sizeof (char *));
155 for (i
= 0; i
< argc
; i
++) {
156 if (argv
[i
][0] == '-') {
158 /* -[0-9]* sets max # records to print */
159 if (isdigit(argv
[i
][1])) {
160 maxrec
= atoi(argv
[i
]+1);
164 for (j
= 1; argv
[i
][j
] != '\0'; ++j
) {
165 switch (argv
[i
][j
]) {
167 /* -f name sets filename of wtmp file */
169 if (argv
[i
][j
+1] != '\0') {
170 wtmpfile
= &argv
[i
][j
+1];
171 } else if (i
+1 < argc
) {
172 wtmpfile
= argv
[++i
];
174 (void) fprintf(stderr
,
175 gettext("last: argument to "
177 (void) fprintf(stderr
,
183 /* -n number sets max # records to print */
187 if (argv
[i
][j
+1] != '\0') {
189 } else if (i
+1 < argc
) {
192 (void) fprintf(stderr
,
193 gettext("last: argument to "
195 (void) fprintf(stderr
,
200 if (!isdigit(*arg
)) {
201 (void) fprintf(stderr
,
202 gettext("last: argument to "
203 "-n is not a number\n"));
204 (void) fprintf(stderr
,
212 /* -a displays hostname last on the line */
217 /* -l turns on long dates and times */
223 (void) fprintf(stderr
, gettext(USAGE
));
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
];
238 /* tty number. Prepend "tty". */
239 names
[names_num
] = strspl("tty", argv
[i
]);
244 wtmp
= open(wtmpfile
, O_RDONLY
);
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
);
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 "));
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
++) {
270 if (ttnames
[i
] == NULL
) {
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].
280 (void) strlcpy(ttnames
[i
], bp
->ut_line
,
282 (void) strlcpy(ttnames
[i
]+LMAX
+1,
284 (void) strlcpy(ttnames
[i
]+LMAX
+HMAX
+2,
286 record_time(&otime
, &print
,
289 } else if (linehostnameq(ttnames
[i
],
290 bp
->ut_line
, ut_host
, ut_user
)) {
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';
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
,
311 /* write seconds and year if -l specified */
313 fpos
= snprintf(timef
, sizeof (timef
),
317 fpos
= snprintf(timef
, sizeof (timef
),
322 if (!lineq(bp
->ut_line
, "system boot") &&
323 !lineq(bp
->ut_line
, "system down")) {
325 bp
->ut_type
== USER_PROCESS
) {
327 if (fpos
< sizeof (timef
)) {
328 /* timef still has room */
329 (void) snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
330 gettext(" still logged in"));
339 * See other notes on "down"
341 * "-" means "until". This
342 * is displayed after the
343 * starting time as in:
345 * You probably don't want to
346 * translate this. Should you
347 * decide to translate this,
348 * translate "- %5.5s" too.
351 if (fpos
< sizeof (timef
)) {
352 /* timef still has room */
353 chrcnt
= snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
354 gettext("- %s"), crmsg
);
360 if (fpos
< sizeof (timef
)) {
361 /* timef still has room */
363 chrcnt
= snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
364 gettext("- %8.8s"), ctime(&otime
) + 11);
366 chrcnt
= snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
367 gettext("- %5.5s"), ctime(&otime
) + 11);
373 delta
= otime
- bp
->ut_xtime
;
374 if (delta
< SECDAY
) {
376 if (fpos
< sizeof (timef
)) {
377 /* timef still has room */
379 (void) snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
380 gettext(" (%8.8s)"), asctime(gmtime(&delta
)) + 11);
382 (void) snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
383 gettext(" (%5.5s)"), asctime(gmtime(&delta
)) + 11);
390 if (fpos
< sizeof (timef
)) {
391 /* timef still has room */
393 (void) snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
394 gettext(" (%ld+%8.8s)"), delta
/ SECDAY
,
395 asctime(gmtime(&delta
)) + 11);
397 (void) snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
398 gettext(" (%ld+%5.5s)"), delta
/ SECDAY
,
399 asctime(gmtime(&delta
)) + 11);
408 (void) printf("%-.*s %-.*s\n",
409 strlen(timef
), timef
,
410 strlen(hostf
), hostf
);
413 "%-16.16s %-.*s\n", hostf
,
414 strlen(timef
), timef
);
418 "%-35.35s %-.*s\n", timef
,
419 strlen(hostf
), hostf
);
422 "%-16.16s %-.35s\n", hostf
,
425 (void) fflush(stdout
);
426 if (++outrec
>= maxrec
)
430 * when the system is down or crashed.
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
;
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.
443 crmsg
= gettext("down ");
445 print
= 0; /* reset the print flag */
448 ct
= ctime(&buf
[0].ut_xtime
);
450 (void) printf(gettext("\nwtmp begins %10.10s %13.13s \n"), ct
,
453 (void) printf(gettext("\nwtmp begins %10.10s %5.5s \n"), ct
,
457 /* free() called to prevent lint warning about names */
467 static char **tmpttnames
;
468 static time_t *tmplogouts
;
471 tmpttnames
= reallocarray(ttnames
, lines
, sizeof (char *));
472 tmplogouts
= reallocarray(logouts
, lines
, sizeof (time_t));
473 if (tmpttnames
== NULL
|| tmplogouts
== NULL
) {
474 (void) fprintf(stderr
, gettext("Out of memory \n"));
477 ttnames
= tmpttnames
;
478 logouts
= tmplogouts
;
480 for (j
= lines
-CHUNK_SIZE
; j
< lines
; j
++) {
482 logouts
[j
] = bootxtime
;
489 ttnames
[i
] = (char *)malloc(LMAX
+ HMAX
+ NMAX
+ 3);
490 if (ttnames
[i
] == NULL
) {
491 (void) fprintf(stderr
, gettext("Out of memory \n "));
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
);
511 want(struct utmpx
*bp
, char **host
, char **user
)
515 char *zerostr
= "\0";
517 *host
= zerostr
; *user
= zerostr
;
519 /* if ut_line = dtremote for the users who did dtremote login */
520 if (strncmp(bp
->ut_line
, "dtremote", 8) == 0) {
524 /* if ut_line = dtlocal for the users who did a dtlocal login */
525 else if (strncmp(bp
->ut_line
, "dtlocal", 7) == 0) {
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
534 if ((bp
->ut_type
== BOOT_TIME
) || (bp
->ut_type
== DOWN_TIME
))
535 (void) strcpy(bp
->ut_user
, "reboot");
537 if (bp
->ut_type
!= USER_PROCESS
&& bp
->ut_type
!= DEAD_PROCESS
&&
538 bp
->ut_type
!= BOOT_TIME
&& bp
->ut_type
!= DOWN_TIME
)
541 if (bp
->ut_user
[0] == '.')
544 if (names_num
== 0) {
545 if (bp
->ut_line
[0] != '\0')
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))) {
562 strspl(char *left
, char *right
)
564 size_t ressize
= strlen(left
) + strlen(right
) + 1;
566 char *res
= malloc(ressize
);
572 (void) strlcpy(res
, left
, ressize
);
573 (void) strlcat(res
, right
, ressize
);
578 record_time(time_t *otime
, int *print
, int i
, struct utmpx
*bp
)
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
))