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) 2013 Gary Mills 24 * 25 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 26 * Use is subject to license terms. 27 */ 28 29 /* 30 * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T 31 * All Rights Reserved 32 */ 33 34 /* 35 * University Copyright- Copyright (c) 1982, 1986, 1988 36 * The Regents of the University of California 37 * All Rights Reserved 38 * 39 * University Acknowledgment- Portions of this document are derived from 40 * software developed by the University of California, Berkeley, and its 41 * contributors. 42 */ 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 * Use the full lengths from utmpx for NMAX, LMAX and HMAX . 62 */ 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)) 66 67 /* Print minimum field widths. */ 68 #define LOGIN_WIDTH 8 69 #define LINE_WIDTH 12 70 71 #define SECDAY (24*60*60) 72 #define CHUNK_SIZE 256 73 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)) 79 80 #define USAGE "usage: last [-n number] [-f filename] [-a ] [name | tty] ...\n" 81 82 /* Beware: These are set in main() to exclude the executable name. */ 83 static char **argv; 84 static int argc; 85 static char **names; 86 static int names_num; 87 88 static struct utmpx buf[128]; 89 90 /* 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 95 * the last BOOT_TIME 96 * All elements of the logouts are initialised to bootxtime 97 * everytime the buffer is reallocated. 98 */ 99 100 static char **ttnames; 101 static time_t *logouts; 102 static time_t bootxtime; 103 static int lines; 104 static char timef[128]; 105 static char hostf[HMAX + 1]; 106 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 *); 113 114 int 115 main(int ac, char **av) 116 { 117 int i, j; 118 int aflag = 0; 119 int fpos; /* current position in time format buffer */ 120 int chrcnt; /* # of chars formatted by current sprintf */ 121 int bl, wtmp; 122 char *ct; 123 char *ut_host; 124 char *ut_user; 125 struct utmpx *bp; 126 time_t otime; 127 struct stat stb; 128 int print = 0; 129 char *crmsg = (char *)0; 130 long outrec = 0; 131 long maxrec = 0x7fffffffL; 132 char *wtmpfile = "/var/adm/wtmpx"; 133 size_t hostf_len; 134 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. */ 138 #endif 139 (void) textdomain(TEXT_DOMAIN); 140 141 (void) time(&buf[0].ut_xtime); 142 ac--, av++; 143 argc = ac; 144 argv = av; 145 names = malloc(argc * sizeof (char *)); 146 if (names == NULL) { 147 perror("last"); 148 exit(2); 149 } 150 names_num = 0; 151 for (i = 0; i < argc; i++) { 152 if (argv[i][0] == '-') { 153 154 /* -[0-9]* sets max # records to print */ 155 if (isdigit(argv[i][1])) { 156 maxrec = atoi(argv[i]+1); 157 continue; 158 } 159 160 for (j = 1; argv[i][j] != '\0'; ++j) { 161 switch (argv[i][j]) { 162 163 /* -f name sets filename of wtmp file */ 164 case 'f': 165 if (argv[i][j+1] != '\0') { 166 wtmpfile = &argv[i][j+1]; 167 } else if (i+1 < argc) { 168 wtmpfile = argv[++i]; 169 } else { 170 (void) fprintf(stderr, 171 gettext("last: argument to " 172 "-f is missing\n")); 173 (void) fprintf(stderr, 174 gettext(USAGE)); 175 exit(1); 176 } 177 goto next_word; 178 179 /* -n number sets max # records to print */ 180 case 'n': { 181 char *arg; 182 183 if (argv[i][j+1] != '\0') { 184 arg = &argv[i][j+1]; 185 } else if (i+1 < argc) { 186 arg = argv[++i]; 187 } else { 188 (void) fprintf(stderr, 189 gettext("last: argument to " 190 "-n is missing\n")); 191 (void) fprintf(stderr, 192 gettext(USAGE)); 193 exit(1); 194 } 195 196 if (!isdigit(*arg)) { 197 (void) fprintf(stderr, 198 gettext("last: argument to " 199 "-n is not a number\n")); 200 (void) fprintf(stderr, 201 gettext(USAGE)); 202 exit(1); 203 } 204 maxrec = atoi(arg); 205 goto next_word; 206 } 207 208 /* -a displays hostname last on the line */ 209 case 'a': 210 aflag++; 211 break; 212 213 default: 214 (void) fprintf(stderr, gettext(USAGE)); 215 exit(1); 216 } 217 } 218 219 next_word: 220 continue; 221 } 222 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]; 227 ++names_num; 228 } else { 229 /* tty number. Prepend "tty". */ 230 names[names_num] = strspl("tty", argv[i]); 231 ++names_num; 232 } 233 } 234 235 wtmp = open(wtmpfile, 0); 236 if (wtmp < 0) { 237 perror(wtmpfile); 238 exit(1); 239 } 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); 245 } 246 lines = CHUNK_SIZE; 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 ")); 251 exit(2); 252 } 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++) { 259 if (i == lines) 260 reallocate_buffer(); 261 if (ttnames[i] == NULL) { 262 memory_alloc(i); 263 /* 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]. 270 */ 271 (void) strlcpy(ttnames[i], bp->ut_line, 272 LMAX+1); 273 (void) strlcpy(ttnames[i]+LMAX+1, 274 ut_host, HMAX+1); 275 (void) strlcpy(ttnames[i]+LMAX+HMAX+2, 276 ut_user, NMAX+1); 277 record_time(&otime, &print, 278 i, bp); 279 break; 280 } else if (linehostnameq(ttnames[i], 281 bp->ut_line, ut_host, ut_user)) { 282 record_time(&otime, 283 &print, i, bp); 284 break; 285 } 286 } 287 } 288 if (print) { 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'; 293 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, 301 bp->ut_host); 302 fpos = snprintf(timef, sizeof (timef), 303 "%10.10s %5.5s ", 304 ct, 11 + ct); 305 if (!lineq(bp->ut_line, "system boot") && 306 !lineq(bp->ut_line, "system down")) { 307 if (otime == 0 && 308 bp->ut_type == USER_PROCESS) { 309 310 if (fpos < sizeof (timef)) { 311 /* timef still has room */ 312 (void) snprintf(timef + fpos, sizeof (timef) - fpos, 313 gettext(" still logged in")); 314 } 315 316 } else { 317 time_t delta; 318 if (otime < 0) { 319 otime = -otime; 320 /* 321 * TRANSLATION_NOTE 322 * See other notes on "down" 323 * and "- %5.5s". 324 * "-" means "until". This 325 * is displayed after the 326 * starting time as in: 327 * 16:20 - down 328 * You probably don't want to 329 * translate this. Should you 330 * decide to translate this, 331 * translate "- %5.5s" too. 332 */ 333 334 if (fpos < sizeof (timef)) { 335 /* timef still has room */ 336 chrcnt = snprintf(timef + fpos, sizeof (timef) - fpos, 337 gettext("- %s"), crmsg); 338 fpos += chrcnt; 339 } 340 341 } else { 342 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); 347 fpos += chrcnt; 348 } 349 350 } 351 delta = otime - bp->ut_xtime; 352 if (delta < SECDAY) { 353 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); 358 } 359 360 } else { 361 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); 367 } 368 369 } 370 } 371 } 372 if (aflag) 373 (void) printf("%-35.35s %-.*s\n", 374 timef, strlen(hostf), hostf); 375 else 376 (void) printf("%-16.16s %-.35s\n", 377 hostf, timef); 378 (void) fflush(stdout); 379 if (++outrec >= maxrec) 380 exit(0); 381 } 382 /* 383 * when the system is down or crashed. 384 */ 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; 389 /* 390 * TRANSLATION_NOTE 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. 395 */ 396 crmsg = gettext("down "); 397 } 398 print = 0; /* reset the print flag */ 399 } 400 } 401 ct = ctime(&buf[0].ut_xtime); 402 (void) printf(gettext("\nwtmp begins %10.10s %5.5s \n"), ct, ct + 11); 403 404 /* free() called to prevent lint warning about names */ 405 free(names); 406 407 return (0); 408 } 409 410 static void 411 reallocate_buffer() 412 { 413 int j; 414 static char **tmpttnames; 415 static time_t *tmplogouts; 416 417 lines += CHUNK_SIZE; 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")); 422 exit(2); 423 } else { 424 ttnames = tmpttnames; 425 logouts = tmplogouts; 426 } 427 for (j = lines-CHUNK_SIZE; j < lines; j++) { 428 ttnames[j] = NULL; 429 logouts[j] = bootxtime; 430 } 431 } 432 433 static void 434 memory_alloc(int i) 435 { 436 ttnames[i] = (char *)malloc(LMAX + HMAX + NMAX + 3); 437 if (ttnames[i] == NULL) { 438 (void) fprintf(stderr, gettext("Out of memory \n ")); 439 exit(2); 440 } 441 } 442 443 static void 444 onintr(int signo) 445 { 446 char *ct; 447 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); 453 if (signo == SIGINT) 454 exit(1); 455 } 456 457 static int 458 want(struct utmpx *bp, char **host, char **user) 459 { 460 char **name; 461 int i; 462 char *zerostr = "\0"; 463 464 *host = zerostr; *user = zerostr; 465 466 /* if ut_line = dtremote for the users who did dtremote login */ 467 if (strncmp(bp->ut_line, "dtremote", 8) == 0) { 468 *host = bp->ut_host; 469 *user = bp->ut_user; 470 } 471 /* if ut_line = dtlocal for the users who did a dtlocal login */ 472 else if (strncmp(bp->ut_line, "dtlocal", 7) == 0) { 473 *host = bp->ut_host; 474 *user = bp->ut_user; 475 } 476 /* 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 480 */ 481 if ((bp->ut_type == BOOT_TIME) || (bp->ut_type == DOWN_TIME)) 482 (void) strcpy(bp->ut_user, "reboot"); 483 484 if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS && 485 bp->ut_type != BOOT_TIME && bp->ut_type != DOWN_TIME) 486 return (0); 487 488 if (bp->ut_user[0] == '.') 489 return (0); 490 491 if (names_num == 0) { 492 if (bp->ut_line[0] != '\0') 493 return (1); 494 } else { 495 name = names; 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))) { 501 return (1); 502 } 503 } 504 } 505 return (0); 506 } 507 508 static char * 509 strspl(char *left, char *right) 510 { 511 size_t ressize = strlen(left) + strlen(right) + 1; 512 513 char *res = malloc(ressize); 514 515 if (res == NULL) { 516 perror("last"); 517 exit(2); 518 } 519 (void) strlcpy(res, left, ressize); 520 (void) strlcat(res, right, ressize); 521 return (res); 522 } 523 524 static void 525 record_time(time_t *otime, int *print, int i, struct utmpx *bp) 526 { 527 *otime = logouts[i]; 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)) 531 *print = 1; 532 else 533 *print = 0; 534 }