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 }