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) 2013 Gary Mills
25 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
26 * Use is subject to license terms.
30 * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
35 * University Copyright- Copyright (c) 1982, 1986, 1988
36 * The Regents of the University of California
39 * University Acknowledgment- Portions of this document are derived from
40 * software developed by the University of California, Berkeley, and its
47 #include <sys/types.h>
61 * Use the full lengths from utmpx for NMAX, LMAX and HMAX .
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))
67 /* Print minimum field widths. */
71 #define SECDAY (24*60*60)
72 #define CHUNK_SIZE 256
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))
80 #define USAGE "usage: last [-n number] [-f filename] [-a ] [name | tty] ...\n"
82 /* Beware: These are set in main() to exclude the executable name. */
88 static struct utmpx buf
[128];
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
96 * All elements of the logouts are initialised to bootxtime
97 * everytime the buffer is reallocated.
100 static char **ttnames
;
101 static time_t *logouts
;
102 static time_t bootxtime
;
104 static char timef
[128];
105 static char hostf
[HMAX
+ 1];
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
*);
115 main(int ac
, char **av
)
119 int fpos
; /* current position in time format buffer */
120 int chrcnt
; /* # of chars formatted by current sprintf */
129 char *crmsg
= (char *)0;
131 long maxrec
= 0x7fffffffL
;
132 char *wtmpfile
= "/var/adm/wtmpx";
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. */
139 (void) textdomain(TEXT_DOMAIN
);
141 (void) time(&buf
[0].ut_xtime
);
145 names
= malloc(argc
* sizeof (char *));
151 for (i
= 0; i
< argc
; i
++) {
152 if (argv
[i
][0] == '-') {
154 /* -[0-9]* sets max # records to print */
155 if (isdigit(argv
[i
][1])) {
156 maxrec
= atoi(argv
[i
]+1);
160 for (j
= 1; argv
[i
][j
] != '\0'; ++j
) {
161 switch (argv
[i
][j
]) {
163 /* -f name sets filename of wtmp file */
165 if (argv
[i
][j
+1] != '\0') {
166 wtmpfile
= &argv
[i
][j
+1];
167 } else if (i
+1 < argc
) {
168 wtmpfile
= argv
[++i
];
170 (void) fprintf(stderr
,
171 gettext("last: argument to "
173 (void) fprintf(stderr
,
179 /* -n number sets max # records to print */
183 if (argv
[i
][j
+1] != '\0') {
185 } else if (i
+1 < argc
) {
188 (void) fprintf(stderr
,
189 gettext("last: argument to "
191 (void) fprintf(stderr
,
196 if (!isdigit(*arg
)) {
197 (void) fprintf(stderr
,
198 gettext("last: argument to "
199 "-n is not a number\n"));
200 (void) fprintf(stderr
,
208 /* -a displays hostname last on the line */
214 (void) fprintf(stderr
, gettext(USAGE
));
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
];
229 /* tty number. Prepend "tty". */
230 names
[names_num
] = strspl("tty", argv
[i
]);
235 wtmp
= open(wtmpfile
, 0);
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
);
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 "));
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
++) {
261 if (ttnames
[i
] == NULL
) {
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].
271 (void) strlcpy(ttnames
[i
], bp
->ut_line
,
273 (void) strlcpy(ttnames
[i
]+LMAX
+1,
275 (void) strlcpy(ttnames
[i
]+LMAX
+HMAX
+2,
277 record_time(&otime
, &print
,
280 } else if (linehostnameq(ttnames
[i
],
281 bp
->ut_line
, ut_host
, ut_user
)) {
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';
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
,
302 fpos
= snprintf(timef
, sizeof (timef
),
305 if (!lineq(bp
->ut_line
, "system boot") &&
306 !lineq(bp
->ut_line
, "system down")) {
308 bp
->ut_type
== USER_PROCESS
) {
310 if (fpos
< sizeof (timef
)) {
311 /* timef still has room */
312 (void) snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
313 gettext(" still logged in"));
322 * See other notes on "down"
324 * "-" means "until". This
325 * is displayed after the
326 * starting time as in:
328 * You probably don't want to
329 * translate this. Should you
330 * decide to translate this,
331 * translate "- %5.5s" too.
334 if (fpos
< sizeof (timef
)) {
335 /* timef still has room */
336 chrcnt
= snprintf(timef
+ fpos
, sizeof (timef
) - fpos
,
337 gettext("- %s"), crmsg
);
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);
351 delta
= otime
- bp
->ut_xtime
;
352 if (delta
< SECDAY
) {
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);
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);
373 (void) printf("%-35.35s %-.*s\n",
374 timef
, strlen(hostf
), hostf
);
376 (void) printf("%-16.16s %-.35s\n",
378 (void) fflush(stdout
);
379 if (++outrec
>= maxrec
)
383 * when the system is down or crashed.
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
;
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.
396 crmsg
= gettext("down ");
398 print
= 0; /* reset the print flag */
401 ct
= ctime(&buf
[0].ut_xtime
);
402 (void) printf(gettext("\nwtmp begins %10.10s %5.5s \n"), ct
, ct
+ 11);
404 /* free() called to prevent lint warning about names */
414 static char **tmpttnames
;
415 static time_t *tmplogouts
;
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"));
424 ttnames
= tmpttnames
;
425 logouts
= tmplogouts
;
427 for (j
= lines
-CHUNK_SIZE
; j
< lines
; j
++) {
429 logouts
[j
] = bootxtime
;
436 ttnames
[i
] = (char *)malloc(LMAX
+ HMAX
+ NMAX
+ 3);
437 if (ttnames
[i
] == NULL
) {
438 (void) fprintf(stderr
, gettext("Out of memory \n "));
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
);
458 want(struct utmpx
*bp
, char **host
, char **user
)
462 char *zerostr
= "\0";
464 *host
= zerostr
; *user
= zerostr
;
466 /* if ut_line = dtremote for the users who did dtremote login */
467 if (strncmp(bp
->ut_line
, "dtremote", 8) == 0) {
471 /* if ut_line = dtlocal for the users who did a dtlocal login */
472 else if (strncmp(bp
->ut_line
, "dtlocal", 7) == 0) {
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
481 if ((bp
->ut_type
== BOOT_TIME
) || (bp
->ut_type
== DOWN_TIME
))
482 (void) strcpy(bp
->ut_user
, "reboot");
484 if (bp
->ut_type
!= USER_PROCESS
&& bp
->ut_type
!= DEAD_PROCESS
&&
485 bp
->ut_type
!= BOOT_TIME
&& bp
->ut_type
!= DOWN_TIME
)
488 if (bp
->ut_user
[0] == '.')
491 if (names_num
== 0) {
492 if (bp
->ut_line
[0] != '\0')
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))) {
509 strspl(char *left
, char *right
)
511 size_t ressize
= strlen(left
) + strlen(right
) + 1;
513 char *res
= malloc(ressize
);
519 (void) strlcpy(res
, left
, ressize
);
520 (void) strlcat(res
, right
, ressize
);
525 record_time(time_t *otime
, int *print
, int i
, struct utmpx
*bp
)
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
))