1 /* 2 * Copyright (c) 1987, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 4. Neither the name of the University nor the names of its contributors 14 * may be used to endorse or promote products derived from this software 15 * without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #ifndef lint 31 static const char copyright[] = 32 "@(#) Copyright (c) 1987, 1993, 1994\n\ 33 The Regents of the University of California. All rights reserved.\n"; 34 #endif /* not lint */ 35 36 #ifndef lint 37 static const char sccsid[] = "@(#)last.c 8.2 (Berkeley) 4/2/94"; 38 #endif /* not lint */ 39 #include <sys/cdefs.h> 40 __FBSDID("$FreeBSD$"); 41 42 #include <sys/param.h> 43 #include <sys/stat.h> 44 45 #include <err.h> 46 #include <errno.h> 47 #include <fcntl.h> 48 #include <langinfo.h> 49 #include <locale.h> 50 #include <paths.h> 51 #include <signal.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 #include <time.h> 56 #include <timeconv.h> 57 #include <unistd.h> 58 #include <utmpx.h> 59 #include <sys/queue.h> 60 61 #define NO 0 /* false/no */ 62 #define YES 1 /* true/yes */ 63 #define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; 64 65 typedef struct arg { 66 char *name; /* argument */ 67 #define REBOOT_TYPE -1 68 #define HOST_TYPE -2 69 #define TTY_TYPE -3 70 #define USER_TYPE -4 71 int type; /* type of arg */ 72 struct arg *next; /* linked list pointer */ 73 } ARG; 74 static ARG *arglist; /* head of linked list */ 75 76 static SLIST_HEAD(, idtab) idlist; 77 78 struct idtab { 79 time_t logout; /* log out time */ 80 char id[sizeof ((struct utmpx *)0)->ut_id]; /* identifier */ 81 SLIST_ENTRY(idtab) list; 82 }; 83 84 static const char *crmsg; /* cause of last reboot */ 85 static time_t currentout; /* current logout value */ 86 static long maxrec; /* records to display */ 87 static const char *file = NULL; /* utx.log file */ 88 static int sflag = 0; /* show delta in seconds */ 89 static int width = 5; /* show seconds in delta */ 90 static int yflag; /* show year */ 91 static int d_first; 92 static int snapfound = 0; /* found snapshot entry? */ 93 static time_t snaptime; /* if != 0, we will only 94 * report users logged in 95 * at this snapshot time 96 */ 97 98 static void addarg(int, char *); 99 static time_t dateconv(char *); 100 static void doentry(struct utmpx *); 101 static void hostconv(char *); 102 static void printentry(struct utmpx *, struct idtab *); 103 static char *ttyconv(char *); 104 static int want(struct utmpx *); 105 static void usage(void); 106 static void wtmp(void); 107 108 static void 109 usage(void) 110 { 111 (void)fprintf(stderr, 112 "usage: last [-swy] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n" 113 " [-n maxrec] [-t tty] [user ...]\n"); 114 exit(1); 115 } 116 117 int 118 main(int argc, char *argv[]) 119 { 120 int ch; 121 char *p; 122 123 (void) setlocale(LC_TIME, ""); 124 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 125 126 maxrec = -1; 127 snaptime = 0; 128 while ((ch = getopt(argc, argv, "0123456789d:f:h:n:st:wy")) != -1) 129 switch (ch) { 130 case '0': case '1': case '2': case '3': case '4': 131 case '5': case '6': case '7': case '8': case '9': 132 /* 133 * kludge: last was originally designed to take 134 * a number after a dash. 135 */ 136 if (maxrec == -1) { 137 p = strchr(argv[optind - 1], ch); 138 if (p == NULL) 139 p = strchr(argv[optind], ch); 140 maxrec = atol(p); 141 if (!maxrec) 142 exit(0); 143 } 144 break; 145 case 'd': 146 snaptime = dateconv(optarg); 147 break; 148 case 'f': 149 file = optarg; 150 break; 151 case 'h': 152 hostconv(optarg); 153 addarg(HOST_TYPE, optarg); 154 break; 155 case 'n': 156 errno = 0; 157 maxrec = strtol(optarg, &p, 10); 158 if (p == optarg || *p != '\0' || errno != 0 || 159 maxrec <= 0) 160 errx(1, "%s: bad line count", optarg); 161 break; 162 case 's': 163 sflag++; /* Show delta as seconds */ 164 break; 165 case 't': 166 addarg(TTY_TYPE, ttyconv(optarg)); 167 break; 168 case 'w': 169 width = 8; 170 break; 171 case 'y': 172 yflag++; 173 break; 174 case '?': 175 default: 176 usage(); 177 } 178 179 if (sflag && width == 8) usage(); 180 181 if (argc) { 182 setlinebuf(stdout); 183 for (argv += optind; *argv; ++argv) { 184 if (strcmp(*argv, "reboot") == 0) 185 addarg(REBOOT_TYPE, *argv); 186 #define COMPATIBILITY 187 #ifdef COMPATIBILITY 188 /* code to allow "last p5" to work */ 189 addarg(TTY_TYPE, ttyconv(*argv)); 190 #endif 191 addarg(USER_TYPE, *argv); 192 } 193 } 194 wtmp(); 195 exit(0); 196 } 197 198 /* 199 * wtmp -- 200 * read through the utx.log file 201 */ 202 static void 203 wtmp(void) 204 { 205 struct utmpx *buf = NULL; 206 struct utmpx *ut; 207 static unsigned int amount = 0; 208 time_t t; 209 char ct[80]; 210 struct tm *tm; 211 212 SLIST_INIT(&idlist); 213 (void)time(&t); 214 215 /* Load the last entries from the file. */ 216 if (setutxdb(UTXDB_LOG, file) != 0) 217 err(1, "%s", file); 218 while ((ut = getutxent()) != NULL) { 219 if (amount % 128 == 0) { 220 buf = realloc(buf, (amount + 128) * sizeof *ut); 221 if (buf == NULL) 222 err(1, "realloc"); 223 } 224 memcpy(&buf[amount++], ut, sizeof *ut); 225 if (t > ut->ut_tv.tv_sec) 226 t = ut->ut_tv.tv_sec; 227 } 228 endutxent(); 229 230 /* Display them in reverse order. */ 231 while (amount > 0) 232 doentry(&buf[--amount]); 233 234 tm = localtime(&t); 235 (void) strftime(ct, sizeof(ct), "%+", tm); 236 printf("\n%s begins %s\n", ((file == NULL) ? "utx.log" : file), ct); 237 } 238 239 /* 240 * doentry -- 241 * process a single utx.log entry 242 */ 243 static void 244 doentry(struct utmpx *bp) 245 { 246 struct idtab *tt; 247 248 /* the machine stopped */ 249 if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) { 250 /* everybody just logged out */ 251 while ((tt = SLIST_FIRST(&idlist)) != NULL) { 252 SLIST_REMOVE_HEAD(&idlist, list); 253 free(tt); 254 } 255 currentout = -bp->ut_tv.tv_sec; 256 crmsg = bp->ut_type != SHUTDOWN_TIME ? 257 "crash" : "shutdown"; 258 /* 259 * if we're in snapshot mode, we want to exit if this 260 * shutdown/reboot appears while we we are tracking the 261 * active range 262 */ 263 if (snaptime && snapfound) 264 exit(0); 265 /* 266 * don't print shutdown/reboot entries unless flagged for 267 */ 268 if (!snaptime && want(bp)) 269 printentry(bp, NULL); 270 return; 271 } 272 /* date got set */ 273 if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) { 274 if (want(bp) && !snaptime) 275 printentry(bp, NULL); 276 return; 277 } 278 279 if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS) 280 return; 281 282 /* find associated identifier */ 283 SLIST_FOREACH(tt, &idlist, list) 284 if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id)) 285 break; 286 287 if (tt == NULL) { 288 /* add new one */ 289 tt = malloc(sizeof(struct idtab)); 290 if (tt == NULL) 291 errx(1, "malloc failure"); 292 tt->logout = currentout; 293 memcpy(tt->id, bp->ut_id, sizeof bp->ut_id); 294 SLIST_INSERT_HEAD(&idlist, tt, list); 295 } 296 297 /* 298 * print record if not in snapshot mode and wanted 299 * or in snapshot mode and in snapshot range 300 */ 301 if (bp->ut_type == USER_PROCESS && (want(bp) || 302 (bp->ut_tv.tv_sec < snaptime && 303 (tt->logout > snaptime || tt->logout < 1)))) { 304 snapfound = 1; 305 printentry(bp, tt); 306 } 307 tt->logout = bp->ut_tv.tv_sec; 308 } 309 310 /* 311 * printentry -- 312 * output an entry 313 * 314 * If `tt' is non-NULL, use it and `crmsg' to print the logout time or 315 * logout type (crash/shutdown) as appropriate. 316 */ 317 static void 318 printentry(struct utmpx *bp, struct idtab *tt) 319 { 320 char ct[80]; 321 struct tm *tm; 322 time_t delta; /* time difference */ 323 time_t t; 324 325 if (maxrec != -1 && !maxrec--) 326 exit(0); 327 t = bp->ut_tv.tv_sec; 328 tm = localtime(&t); 329 (void) strftime(ct, sizeof(ct), d_first ? 330 (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") : 331 (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm); 332 switch (bp->ut_type) { 333 case BOOT_TIME: 334 printf("%-42s", "boot time"); 335 break; 336 case SHUTDOWN_TIME: 337 printf("%-42s", "shutdown time"); 338 break; 339 case OLD_TIME: 340 printf("%-42s", "old time"); 341 break; 342 case NEW_TIME: 343 printf("%-42s", "new time"); 344 break; 345 case USER_PROCESS: 346 printf("%-10s %-8s %-22.22s", 347 bp->ut_user, bp->ut_line, bp->ut_host); 348 break; 349 } 350 printf(" %s%c", ct, tt == NULL ? '\n' : ' '); 351 if (tt == NULL) 352 return; 353 if (!tt->logout) { 354 puts(" still logged in"); 355 return; 356 } 357 if (tt->logout < 0) { 358 tt->logout = -tt->logout; 359 printf("- %s", crmsg); 360 } else { 361 tm = localtime(&tt->logout); 362 (void) strftime(ct, sizeof(ct), "%R", tm); 363 printf("- %s", ct); 364 } 365 delta = tt->logout - bp->ut_tv.tv_sec; 366 if (sflag) { 367 printf(" (%8ld)\n", (long)delta); 368 } else { 369 tm = gmtime(&delta); 370 (void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm); 371 if (delta < 86400) 372 printf(" (%s)\n", ct); 373 else 374 printf(" (%ld+%s)\n", (long)delta / 86400, ct); 375 } 376 } 377 378 /* 379 * want -- 380 * see if want this entry 381 */ 382 static int 383 want(struct utmpx *bp) 384 { 385 ARG *step; 386 387 if (snaptime) 388 return (NO); 389 390 if (!arglist) 391 return (YES); 392 393 for (step = arglist; step; step = step->next) 394 switch(step->type) { 395 case REBOOT_TYPE: 396 if (bp->ut_type == BOOT_TIME || 397 bp->ut_type == SHUTDOWN_TIME) 398 return (YES); 399 break; 400 case HOST_TYPE: 401 if (!strcasecmp(step->name, bp->ut_host)) 402 return (YES); 403 break; 404 case TTY_TYPE: 405 if (!strcmp(step->name, bp->ut_line)) 406 return (YES); 407 break; 408 case USER_TYPE: 409 if (!strcmp(step->name, bp->ut_user)) 410 return (YES); 411 break; 412 } 413 return (NO); 414 } 415 416 /* 417 * addarg -- 418 * add an entry to a linked list of arguments 419 */ 420 static void 421 addarg(int type, char *arg) 422 { 423 ARG *cur; 424 425 if ((cur = malloc(sizeof(ARG))) == NULL) 426 errx(1, "malloc failure"); 427 cur->next = arglist; 428 cur->type = type; 429 cur->name = arg; 430 arglist = cur; 431 } 432 433 /* 434 * hostconv -- 435 * convert the hostname to search pattern; if the supplied host name 436 * has a domain attached that is the same as the current domain, rip 437 * off the domain suffix since that's what login(1) does. 438 */ 439 static void 440 hostconv(char *arg) 441 { 442 static int first = 1; 443 static char *hostdot, name[MAXHOSTNAMELEN]; 444 char *argdot; 445 446 if (!(argdot = strchr(arg, '.'))) 447 return; 448 if (first) { 449 first = 0; 450 if (gethostname(name, sizeof(name))) 451 err(1, "gethostname"); 452 hostdot = strchr(name, '.'); 453 } 454 if (hostdot && !strcasecmp(hostdot, argdot)) 455 *argdot = '\0'; 456 } 457 458 /* 459 * ttyconv -- 460 * convert tty to correct name. 461 */ 462 static char * 463 ttyconv(char *arg) 464 { 465 char *mval; 466 467 /* 468 * kludge -- we assume that all tty's end with 469 * a two character suffix. 470 */ 471 if (strlen(arg) == 2) { 472 /* either 6 for "ttyxx" or 8 for "console" */ 473 if ((mval = malloc(8)) == NULL) 474 errx(1, "malloc failure"); 475 if (!strcmp(arg, "co")) 476 (void)strcpy(mval, "console"); 477 else { 478 (void)strcpy(mval, "tty"); 479 (void)strcpy(mval + 3, arg); 480 } 481 return (mval); 482 } 483 if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1)) 484 return (arg + 5); 485 return (arg); 486 } 487 488 /* 489 * dateconv -- 490 * Convert the snapshot time in command line given in the format 491 * [[CC]YY]MMDDhhmm[.SS]] to a time_t. 492 * Derived from atime_arg1() in usr.bin/touch/touch.c 493 */ 494 static time_t 495 dateconv(char *arg) 496 { 497 time_t timet; 498 struct tm *t; 499 int yearset; 500 char *p; 501 502 /* Start with the current time. */ 503 if (time(&timet) < 0) 504 err(1, "time"); 505 if ((t = localtime(&timet)) == NULL) 506 err(1, "localtime"); 507 508 /* [[CC]YY]MMDDhhmm[.SS] */ 509 if ((p = strchr(arg, '.')) == NULL) 510 t->tm_sec = 0; /* Seconds defaults to 0. */ 511 else { 512 if (strlen(p + 1) != 2) 513 goto terr; 514 *p++ = '\0'; 515 t->tm_sec = ATOI2(p); 516 } 517 518 yearset = 0; 519 switch (strlen(arg)) { 520 case 12: /* CCYYMMDDhhmm */ 521 t->tm_year = ATOI2(arg); 522 t->tm_year *= 100; 523 yearset = 1; 524 /* FALLTHROUGH */ 525 case 10: /* YYMMDDhhmm */ 526 if (yearset) { 527 yearset = ATOI2(arg); 528 t->tm_year += yearset; 529 } else { 530 yearset = ATOI2(arg); 531 if (yearset < 69) 532 t->tm_year = yearset + 2000; 533 else 534 t->tm_year = yearset + 1900; 535 } 536 t->tm_year -= 1900; /* Convert to UNIX time. */ 537 /* FALLTHROUGH */ 538 case 8: /* MMDDhhmm */ 539 t->tm_mon = ATOI2(arg); 540 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 541 t->tm_mday = ATOI2(arg); 542 t->tm_hour = ATOI2(arg); 543 t->tm_min = ATOI2(arg); 544 break; 545 case 4: /* hhmm */ 546 t->tm_hour = ATOI2(arg); 547 t->tm_min = ATOI2(arg); 548 break; 549 default: 550 goto terr; 551 } 552 t->tm_isdst = -1; /* Figure out DST. */ 553 timet = mktime(t); 554 if (timet == -1) 555 terr: errx(1, 556 "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); 557 return timet; 558 } 559