last.c revision 74588
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 * $FreeBSD: head/usr.bin/last/last.c 74588 2001-03-21 19:08:01Z ache $ 34 */ 35 36#ifndef lint 37static char copyright[] = 38"@(#) Copyright (c) 1987, 1993, 1994\n\ 39 The Regents of the University of California. All rights reserved.\n"; 40#endif /* not lint */ 41 42#ifndef lint 43static char sccsid[] = "@(#)last.c 8.2 (Berkeley) 4/2/94"; 44#endif /* not lint */ 45 46#include <sys/param.h> 47#include <sys/stat.h> 48 49#include <err.h> 50#include <fcntl.h> 51#include <langinfo.h> 52#include <locale.h> 53#include <paths.h> 54#include <signal.h> 55#include <stdio.h> 56#include <stdlib.h> 57#include <string.h> 58#include <time.h> 59#include <unistd.h> 60#include <utmp.h> 61#include <sys/queue.h> 62 63#define NO 0 /* false/no */ 64#define YES 1 /* true/yes */ 65 66static struct utmp buf[1024]; /* utmp read buffer */ 67 68typedef struct arg { 69 char *name; /* argument */ 70#define HOST_TYPE -2 71#define TTY_TYPE -3 72#define USER_TYPE -4 73 int type; /* type of arg */ 74 struct arg *next; /* linked list pointer */ 75} ARG; 76ARG *arglist; /* head of linked list */ 77 78LIST_HEAD(ttylisthead, ttytab) ttylist; 79 80struct ttytab { 81 time_t logout; /* log out time */ 82 char tty[UT_LINESIZE + 1]; /* terminal name */ 83 LIST_ENTRY(ttytab) list; 84}; 85 86static long currentout, /* current logout value */ 87 maxrec; /* records to display */ 88static char *file = _PATH_WTMP; /* wtmp file */ 89static int sflag = 0; /* show delta in seconds */ 90static int width = 5; /* show seconds in delta */ 91static int d_first; 92 93void addarg __P((int, char *)); 94void hostconv __P((char *)); 95void onintr __P((int)); 96char *ttyconv __P((char *)); 97int want __P((struct utmp *)); 98void wtmp __P((void)); 99 100void 101usage(void) 102{ 103 (void)fprintf(stderr, 104 "usage: last [-#] [-f file] [-h hostname] [-t tty] [-s|w] [user ...]\n"); 105 exit(1); 106} 107 108int 109main(argc, argv) 110 int argc; 111 char *argv[]; 112{ 113 int ch; 114 char *p; 115 116 (void) setlocale(LC_TIME, ""); 117 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 118 119 maxrec = -1; 120 while ((ch = getopt(argc, argv, "0123456789f:h:st:w")) != -1) 121 switch (ch) { 122 case '0': case '1': case '2': case '3': case '4': 123 case '5': case '6': case '7': case '8': case '9': 124 /* 125 * kludge: last was originally designed to take 126 * a number after a dash. 127 */ 128 if (maxrec == -1) { 129 p = argv[optind - 1]; 130 if (p[0] == '-' && p[1] == ch && !p[2]) 131 maxrec = atol(++p); 132 else 133 maxrec = atol(argv[optind] + 1); 134 if (!maxrec) 135 exit(0); 136 } 137 break; 138 case 'f': 139 file = optarg; 140 break; 141 case 'h': 142 hostconv(optarg); 143 addarg(HOST_TYPE, optarg); 144 break; 145 case 's': 146 sflag++; /* Show delta as seconds */ 147 break; 148 case 't': 149 addarg(TTY_TYPE, ttyconv(optarg)); 150 break; 151 case 'w': 152 width = 8; 153 break; 154 case '?': 155 default: 156 usage(); 157 } 158 159 if (sflag && width == 8) usage(); 160 161 if (argc) { 162 setlinebuf(stdout); 163 for (argv += optind; *argv; ++argv) { 164#define COMPATIBILITY 165#ifdef COMPATIBILITY 166 /* code to allow "last p5" to work */ 167 addarg(TTY_TYPE, ttyconv(*argv)); 168#endif 169 addarg(USER_TYPE, *argv); 170 } 171 } 172 wtmp(); 173 exit(0); 174} 175 176/* 177 * wtmp -- 178 * read through the wtmp file 179 */ 180void 181wtmp() 182{ 183 struct utmp *bp; /* current structure */ 184 struct ttytab *tt, *ttx; /* ttylist entry */ 185 struct stat stb; /* stat of file for size */ 186 long bl; 187 time_t delta; /* time difference */ 188 int bytes, wfd; 189 char *crmsg; 190 char ct[80]; 191 struct tm *tm; 192 193 LIST_INIT(&ttylist); 194 195 if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1) 196 err(1, "%s", file); 197 bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf); 198 199 (void)time(&buf[0].ut_time); 200 (void)signal(SIGINT, onintr); 201 (void)signal(SIGQUIT, onintr); 202 203 while (--bl >= 0) { 204 if (lseek(wfd, (off_t)(bl * sizeof(buf)), L_SET) == -1 || 205 (bytes = read(wfd, buf, sizeof(buf))) == -1) 206 err(1, "%s", file); 207 for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) { 208 /* 209 * if the terminal line is '~', the machine stopped. 210 * see utmp(5) for more info. 211 */ 212 if (bp->ut_line[0] == '~' && !bp->ut_line[1]) { 213 /* everybody just logged out */ 214 for (tt = LIST_FIRST(&ttylist); tt;) { 215 LIST_REMOVE(tt, list); 216 ttx = tt; 217 tt = LIST_NEXT(tt, list); 218 free(ttx); 219 } 220 currentout = -bp->ut_time; 221 crmsg = strncmp(bp->ut_name, "shutdown", 222 UT_NAMESIZE) ? "crash" : "shutdown"; 223 if (want(bp)) { 224 tm = localtime(&bp->ut_time); 225 (void) strftime(ct, sizeof(ct), 226 d_first ? "%a %e %b %R" : 227 "%a %b %e %R", 228 tm); 229 printf("%-*.*s %-*.*s %-*.*s %s\n", 230 UT_NAMESIZE, UT_NAMESIZE, 231 bp->ut_name, UT_LINESIZE, 232 UT_LINESIZE, bp->ut_line, 233 UT_HOSTSIZE, UT_HOSTSIZE, 234 bp->ut_host, ct); 235 if (maxrec != -1 && !--maxrec) 236 return; 237 } 238 continue; 239 } 240 /* 241 * if the line is '{' or '|', date got set; see 242 * utmp(5) for more info. 243 */ 244 if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|') 245 && !bp->ut_line[1]) { 246 if (want(bp)) { 247 tm = localtime(&bp->ut_time); 248 (void) strftime(ct, sizeof(ct), 249 d_first ? "%a %e %b %R" : 250 "%a %b %e %R", 251 tm); 252 printf("%-*.*s %-*.*s %-*.*s %s\n", 253 UT_NAMESIZE, UT_NAMESIZE, bp->ut_name, 254 UT_LINESIZE, UT_LINESIZE, bp->ut_line, 255 UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host, 256 ct); 257 if (maxrec && !--maxrec) 258 return; 259 } 260 continue; 261 } 262 if (bp->ut_name[0] == '\0' || want(bp)) { 263 /* find associated tty */ 264 LIST_FOREACH(tt, &ttylist, list) 265 if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE)) 266 break; 267 268 if (tt == NULL) { 269 /* add new one */ 270 tt = malloc(sizeof(struct ttytab)); 271 if (tt == NULL) 272 err(1, "malloc failure"); 273 tt->logout = currentout; 274 strncpy(tt->tty, bp->ut_line, UT_LINESIZE); 275 LIST_INSERT_HEAD(&ttylist, tt, list); 276 } 277 278 if (bp->ut_name[0]) { 279 /* 280 * when uucp and ftp log in over a network, the entry in 281 * the utmp file is the name plus their process id. See 282 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information. 283 */ 284 if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1)) 285 bp->ut_line[3] = '\0'; 286 else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1)) 287 bp->ut_line[4] = '\0'; 288 tm = localtime(&bp->ut_time); 289 (void) strftime(ct, sizeof(ct), 290 d_first ? "%a %e %b %R" : 291 "%a %b %e %R", 292 tm); 293 printf("%-*.*s %-*.*s %-*.*s %s ", 294 UT_NAMESIZE, UT_NAMESIZE, bp->ut_name, 295 UT_LINESIZE, UT_LINESIZE, bp->ut_line, 296 UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host, 297 ct); 298 if (!tt->logout) 299 puts(" still logged in"); 300 else { 301 if (tt->logout < 0) { 302 tt->logout = -tt->logout; 303 printf("- %s", crmsg); 304 } 305 else { 306 tm = localtime(&tt->logout); 307 (void) strftime(ct, sizeof(ct), "%R", tm); 308 printf("- %s", ct); 309 } 310 delta = tt->logout - bp->ut_time; 311 if ( sflag ) { 312 printf(" (%8lu)\n", 313 delta); 314 } else { 315 tm = gmtime(&delta); 316 (void) strftime(ct, sizeof(ct), 317 width >= 8 ? "%T" : "%R", 318 tm); 319 if (delta < 86400) 320 printf(" (%s)\n", ct); 321 else 322 printf(" (%ld+%s)\n", 323 delta / 86400, ct); 324 } 325 } 326 LIST_REMOVE(tt, list); 327 free(tt); 328 if (maxrec != -1 && !--maxrec) 329 return; 330 } else { 331 tt->logout = bp->ut_time; 332 } 333 } 334 } 335 } 336 tm = localtime(&buf[0].ut_time); 337 (void) strftime(ct, sizeof(ct), "\nwtmp begins %c\n", tm); 338 printf("%s", ct); 339} 340 341/* 342 * want -- 343 * see if want this entry 344 */ 345int 346want(bp) 347 struct utmp *bp; 348{ 349 ARG *step; 350 351 if (!arglist) 352 return (YES); 353 354 for (step = arglist; step; step = step->next) 355 switch(step->type) { 356 case HOST_TYPE: 357 if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE)) 358 return (YES); 359 break; 360 case TTY_TYPE: 361 if (!strncmp(step->name, bp->ut_line, UT_LINESIZE)) 362 return (YES); 363 break; 364 case USER_TYPE: 365 if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE)) 366 return (YES); 367 break; 368 } 369 return (NO); 370} 371 372/* 373 * addarg -- 374 * add an entry to a linked list of arguments 375 */ 376void 377addarg(type, arg) 378 int type; 379 char *arg; 380{ 381 ARG *cur; 382 383 if (!(cur = (ARG *)malloc((u_int)sizeof(ARG)))) 384 err(1, "malloc failure"); 385 cur->next = arglist; 386 cur->type = type; 387 cur->name = arg; 388 arglist = cur; 389} 390 391/* 392 * hostconv -- 393 * convert the hostname to search pattern; if the supplied host name 394 * has a domain attached that is the same as the current domain, rip 395 * off the domain suffix since that's what login(1) does. 396 */ 397void 398hostconv(arg) 399 char *arg; 400{ 401 static int first = 1; 402 static char *hostdot, name[MAXHOSTNAMELEN]; 403 char *argdot; 404 405 if (!(argdot = strchr(arg, '.'))) 406 return; 407 if (first) { 408 first = 0; 409 if (gethostname(name, sizeof(name))) 410 err(1, "gethostname"); 411 hostdot = strchr(name, '.'); 412 } 413 if (hostdot && !strcasecmp(hostdot, argdot)) 414 *argdot = '\0'; 415} 416 417/* 418 * ttyconv -- 419 * convert tty to correct name. 420 */ 421char * 422ttyconv(arg) 423 char *arg; 424{ 425 char *mval; 426 427 /* 428 * kludge -- we assume that all tty's end with 429 * a two character suffix. 430 */ 431 if (strlen(arg) == 2) { 432 /* either 6 for "ttyxx" or 8 for "console" */ 433 if (!(mval = malloc((u_int)8))) 434 err(1, "malloc failure"); 435 if (!strcmp(arg, "co")) 436 (void)strcpy(mval, "console"); 437 else { 438 (void)strcpy(mval, "tty"); 439 (void)strcpy(mval + 3, arg); 440 } 441 return (mval); 442 } 443 if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1)) 444 return (arg + 5); 445 return (arg); 446} 447 448/* 449 * onintr -- 450 * on interrupt, we inform the user how far we've gotten 451 */ 452void 453onintr(signo) 454 int signo; 455{ 456 char ct[80]; 457 struct tm *tm; 458 459 tm = localtime(&buf[0].ut_time); 460 (void) strftime(ct, sizeof(ct), 461 d_first ? "%a %e %b %R" : "%a %b %e %R", 462 tm); 463 printf("\ninterrupted %s\n", ct); 464 if (signo == SIGINT) 465 exit(1); 466 (void)fflush(stdout); /* fix required for rsh */ 467} 468