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