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 } 532