1/* 2 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights 7 * Reserved. This file contains Original Code and/or Modifications of 8 * Original Code as defined in and that are subject to the Apple Public 9 * Source License Version 1.0 (the 'License'). You may not use this file 10 * except in compliance with the License. Please obtain a copy of the 11 * License at http://www.apple.com/publicsource and read it before using 12 * this file. 13 * 14 * The Original Code and all software distributed under the License are 15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the 19 * License for the specific language governing rights and limitations 20 * under the License." 21 * 22 * @APPLE_LICENSE_HEADER_END@ 23 */ 24/* 25 * Copyright (c) 1994 Christopher G. Demetriou. 26 * @(#)Copyright (c) 1994, Simon J. Gerraty. 27 * 28 * This is free software. It comes with NO WARRANTY. 29 * Permission to use, modify and distribute this source code 30 * is granted subject to the following conditions. 31 * 1/ that the above copyright notice and this notice 32 * are preserved in all copies and that due credit be given 33 * to the author. 34 * 2/ that any changes to this code are clearly commented 35 * as such so that the author does not get blamed for bugs 36 * other than his own. 37 */ 38 39#ifndef lint 40#include <sys/cdefs.h> 41__unused static char rcsid[] = "$Id: ac.c,v 1.2 2006/02/07 05:51:22 lindak Exp $"; 42#endif 43 44#include <sys/types.h> 45#include <sys/file.h> 46#include <sys/time.h> 47#include <err.h> 48#include <errno.h> 49#include <pwd.h> 50#include <stdio.h> 51#include <stdlib.h> 52#include <string.h> 53#include <utmpx.h> 54#include <unistd.h> 55 56#define UT_NAMESIZE 8 /* from utmp.h; only for formatting */ 57 58/* 59 * this is for our list of currently logged in sessions 60 */ 61struct utmp_list { 62 struct utmp_list *next; 63 struct utmpx usr; 64}; 65 66/* 67 * this is for our list of users that are accumulating time. 68 */ 69struct user_list { 70 struct user_list *next; 71 char name[_UTX_USERSIZE+1]; 72 time_t secs; 73}; 74 75/* 76 * this is for chosing whether to ignore a login 77 */ 78struct tty_list { 79 struct tty_list *next; 80 char name[_UTX_USERSIZE+3]; 81 int len; 82 int ret; 83}; 84 85/* 86 * globals - yes yuk 87 */ 88#ifdef CONSOLE_TTY 89static char *Console = CONSOLE_TTY; 90#endif 91static time_t Total = 0; 92static time_t FirstTime = 0; 93static int Flags = 0; 94static struct user_list *Users = NULL; 95static struct tty_list *Ttys = NULL; 96 97#define NEW(type) (type *)malloc(sizeof (type)) 98 99#define AC_W 1 /* not _PATH_WTMP */ 100#define AC_D 2 /* daily totals (ignore -p) */ 101#define AC_P 4 /* per-user totals */ 102#define AC_U 8 /* specified users only */ 103#define AC_T 16 /* specified ttys only */ 104 105#ifdef DEBUG 106static int Debug = 0; 107#endif 108 109int main __P((int, char **)); 110int ac __P((void)); 111struct tty_list *add_tty __P((char *)); 112int do_tty __P((char *)); 113struct utmp_list *log_in __P((struct utmp_list *, struct utmpx *)); 114struct utmp_list *log_out __P((struct utmp_list *, struct utmpx *)); 115int on_console __P((struct utmp_list *)); 116void show __P((char *, time_t)); 117void show_today __P((struct user_list *, struct utmp_list *, 118 time_t)); 119void show_users __P((struct user_list *)); 120struct user_list *update_user __P((struct user_list *, char *, time_t)); 121void usage __P((void)); 122 123struct tty_list * 124add_tty(name) 125 char *name; 126{ 127 struct tty_list *tp; 128 register char *rcp; 129 130 Flags |= AC_T; 131 132 if ((tp = NEW(struct tty_list)) == NULL) 133 err(1, "malloc"); 134 tp->len = 0; /* full match */ 135 tp->ret = 1; /* do if match */ 136 if (*name == '!') { /* don't do if match */ 137 tp->ret = 0; 138 name++; 139 } 140 (void)strncpy(tp->name, name, sizeof (tp->name) - 1); 141 tp->name[sizeof (tp->name) - 1] = '\0'; 142 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ 143 *rcp = '\0'; 144 tp->len = (int)strlen(tp->name); /* match len bytes only */ 145 } 146 tp->next = Ttys; 147 Ttys = tp; 148 return Ttys; 149} 150 151/* 152 * should we process the named tty? 153 */ 154int 155do_tty(name) 156 char *name; 157{ 158 struct tty_list *tp; 159 int def_ret = 0; 160 161 for (tp = Ttys; tp != NULL; tp = tp->next) { 162 if (tp->ret == 0) /* specific don't */ 163 def_ret = 1; /* default do */ 164 if (tp->len != 0) { 165 if (strncmp(name, tp->name, tp->len) == 0) 166 return tp->ret; 167 } else { 168 if (strncmp(name, tp->name, sizeof (tp->name)) == 0) 169 return tp->ret; 170 } 171 } 172 return def_ret; 173} 174 175#ifdef CONSOLE_TTY 176/* 177 * is someone logged in on Console? 178 */ 179int 180on_console(head) 181 struct utmp_list *head; 182{ 183 struct utmp_list *up; 184 185 for (up = head; up; up = up->next) { 186 if (strncmp(up->usr.ut_line, Console, 187 sizeof (up->usr.ut_line)) == 0) 188 return 1; 189 } 190 return 0; 191} 192#endif 193 194/* 195 * update user's login time 196 */ 197struct user_list * 198update_user(head, name, secs) 199 struct user_list *head; 200 char *name; 201 time_t secs; 202{ 203 struct user_list *up; 204 205 for (up = head; up != NULL; up = up->next) { 206 if (strncmp(up->name, name, sizeof (up->name)) == 0) { 207 up->secs += secs; 208 Total += secs; 209 return head; 210 } 211 } 212 /* 213 * not found so add new user unless specified users only 214 */ 215 if (Flags & AC_U) 216 return head; 217 218 if ((up = NEW(struct user_list)) == NULL) 219 err(1, "malloc"); 220 up->next = head; 221 (void)strncpy(up->name, name, sizeof (up->name) - 1); 222 up->name[sizeof (up->name) - 1] = '\0'; /* paranoid! */ 223 up->secs = secs; 224 Total += secs; 225 return up; 226} 227 228int 229main(argc, argv) 230 int argc; 231 char **argv; 232{ 233 FILE *fp; 234 int c; 235 236 fp = NULL; 237 while ((c = getopt(argc, argv, "Dc:dpt:w:")) != EOF) { 238 switch (c) { 239#ifdef DEBUG 240 case 'D': 241 Debug++; 242 break; 243#endif 244 case 'c': 245#ifdef CONSOLE_TTY 246 Console = optarg; 247#else 248 usage(); /* XXX */ 249#endif 250 break; 251 case 'd': 252 Flags |= AC_D; 253 break; 254 case 'p': 255 Flags |= AC_P; 256 break; 257 case 't': /* only do specified ttys */ 258 add_tty(optarg); 259 break; 260 case 'w': 261 wtmpxname(optarg); 262 break; 263 case '?': 264 default: 265 usage(); 266 break; 267 } 268 } 269 if (optind < argc) { 270 /* 271 * initialize user list 272 */ 273 for (; optind < argc; optind++) { 274 Users = update_user(Users, argv[optind], 0L); 275 } 276 Flags |= AC_U; /* freeze user list */ 277 } 278 if (Flags & AC_D) 279 Flags &= ~AC_P; 280 ac(); 281 282 return 0; 283} 284 285/* 286 * print login time in decimal hours 287 */ 288void 289show(name, secs) 290 char *name; 291 time_t secs; 292{ 293 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name, 294 ((double)secs / 3600)); 295} 296 297void 298show_users(list) 299 struct user_list *list; 300{ 301 struct user_list *lp; 302 303 for (lp = list; lp; lp = lp->next) 304 show(lp->name, lp->secs); 305} 306 307/* 308 * print total login time for 24hr period in decimal hours 309 */ 310void 311show_today(users, logins, secs) 312 struct user_list *users; 313 struct utmp_list *logins; 314 time_t secs; 315{ 316 struct user_list *up; 317 struct utmp_list *lp; 318 char date[64]; 319 time_t yesterday = secs - 1; 320 321 (void)strftime(date, sizeof (date), "%b %e total", 322 localtime(&yesterday)); 323 324 /* restore the missing second */ 325 yesterday++; 326 327 for (lp = logins; lp != NULL; lp = lp->next) { 328 secs = yesterday - lp->usr.ut_tv.tv_sec; 329 Users = update_user(Users, lp->usr.ut_user, secs); 330 lp->usr.ut_tv.tv_sec = yesterday; /* as if they just logged in */ 331 } 332 secs = 0; 333 for (up = users; up != NULL; up = up->next) { 334 secs += up->secs; 335 up->secs = 0; /* for next day */ 336 } 337 if (secs) 338 (void)printf("%s %11.2f\n", date, ((double)secs / 3600)); 339} 340 341/* 342 * log a user out and update their times. 343 * if ut_line is "~", we log all users out as the system has 344 * been shut down. 345 */ 346struct utmp_list * 347log_out(head, up) 348 struct utmp_list *head; 349 struct utmpx *up; 350{ 351 struct utmp_list *lp, *lp2, *tlp; 352 time_t secs; 353 354 for (lp = head, lp2 = NULL; lp != NULL; ) 355 if (up->ut_type == BOOT_TIME || up->ut_type == SHUTDOWN_TIME || strncmp(lp->usr.ut_line, up->ut_line, 356 sizeof (up->ut_line)) == 0) { 357 secs = up->ut_tv.tv_sec - lp->usr.ut_tv.tv_sec; 358 Users = update_user(Users, lp->usr.ut_user, secs); 359#ifdef DEBUG 360 if (Debug) 361 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n", 362 19, ctime(&up->ut_tv.tv_sec), 363 (int)sizeof (lp->usr.ut_line), lp->usr.ut_line, 364 (int)sizeof (lp->usr.ut_user), lp->usr.ut_user, 365 secs / 3600, (secs % 3600) / 60, secs % 60); 366#endif 367 /* 368 * now lose it 369 */ 370 tlp = lp; 371 lp = lp->next; 372 if (tlp == head) 373 head = lp; 374 else if (lp2 != NULL) 375 lp2->next = lp; 376 free(tlp); 377 } else { 378 lp2 = lp; 379 lp = lp->next; 380 } 381 return head; 382} 383 384 385/* 386 * if do_tty says ok, login a user 387 */ 388struct utmp_list * 389log_in(head, up) 390 struct utmp_list *head; 391 struct utmpx *up; 392{ 393 struct utmp_list *lp; 394 395 /* 396 * this could be a login. if we're not dealing with 397 * the console name, say it is. 398 * 399 * If we are, and if ut_host==":0.0" we know that it 400 * isn't a real login. _But_ if we have not yet recorded 401 * someone being logged in on Console - due to the wtmp 402 * file starting after they logged in, we'll pretend they 403 * logged in, at the start of the wtmp file. 404 */ 405 406#ifdef CONSOLE_TTY 407 if (up->ut_host[0] == ':') { 408 /* 409 * SunOS 4.0.2 does not treat ":0.0" as special but we 410 * do. 411 */ 412 if (on_console(head)) 413 return head; 414 /* 415 * ok, no recorded login, so they were here when wtmp 416 * started! Adjust ut_tv.tv_sec! 417 */ 418 up->ut_tv.tv_sec = FirstTime; 419 /* 420 * this allows us to pick the right logout 421 */ 422 (void)strncpy(up->ut_line, Console, sizeof (up->ut_line) - 1); 423 up->ut_line[sizeof (up->ut_line) - 1] = '\0'; /* paranoid! */ 424 } 425#endif 426 /* 427 * If we are doing specified ttys only, we ignore 428 * anything else. 429 */ 430 if (Flags & AC_T) 431 if (!do_tty(up->ut_line)) 432 return head; 433 434 /* 435 * go ahead and log them in 436 */ 437 if ((lp = NEW(struct utmp_list)) == NULL) 438 err(1, "malloc"); 439 lp->next = head; 440 head = lp; 441 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmpx)); 442#ifdef DEBUG 443 if (Debug) { 444 printf("%-.*s %-.*s: %-.*s logged in", 19, 445 ctime(&lp->usr.ut_tv.tv_sec), (int)sizeof (up->ut_line), 446 up->ut_line, (int)sizeof (up->ut_user), up->ut_user); 447 if (*up->ut_host) 448 printf(" (%-.*s)", (int)sizeof (up->ut_host), up->ut_host); 449 putchar('\n'); 450 } 451#endif 452 return head; 453} 454 455int 456ac() 457{ 458 struct utmp_list *lp, *head = NULL; 459 struct utmpx *u, end; 460 struct tm *ltm; 461 time_t secs = 0; 462 int day = -1; 463 464 setutxent_wtmp(1); /* read in forward direction */ 465 while ((u = getutxent_wtmp()) != NULL) { 466 if (!FirstTime) 467 FirstTime = u->ut_tv.tv_sec; 468 if (Flags & AC_D) { 469 ltm = localtime(&u->ut_tv.tv_sec); 470 if (day >= 0 && day != ltm->tm_yday) { 471 day = ltm->tm_yday; 472 /* 473 * print yesterday's total 474 */ 475 secs = u->ut_tv.tv_sec; 476 secs -= ltm->tm_sec; 477 secs -= 60 * ltm->tm_min; 478 secs -= 3600 * ltm->tm_hour; 479 show_today(Users, head, secs); 480 } else 481 day = ltm->tm_yday; 482 } 483 switch(u->ut_type) { 484 case OLD_TIME: 485 secs = u->ut_tv.tv_sec; 486 break; 487 case NEW_TIME: 488 secs -= u->ut_tv.tv_sec; 489 /* 490 * adjust time for those logged in 491 */ 492 for (lp = head; lp != NULL; lp = lp->next) 493 lp->usr.ut_tv.tv_sec -= secs; 494 break; 495 case BOOT_TIME: /* reboot or shutdown */ 496 case SHUTDOWN_TIME: 497 head = log_out(head, u); 498 FirstTime = u->ut_tv.tv_sec; /* shouldn't be needed */ 499 break; 500 case USER_PROCESS: 501 /* 502 * if they came in on tty[p-y]*, then it is only 503 * a login session if the ut_host field is non-empty 504 */ 505 if (strncmp(u->ut_line, "tty", 3) != 0 || 506 strchr("pqrstuvwxy", u->ut_line[3]) == 0 || 507 *u->ut_host != '\0') 508 head = log_in(head, u); 509 break; 510 case DEAD_PROCESS: 511 head = log_out(head, u); 512 break; 513 } 514 } 515 endutxent_wtmp(); 516 bzero(&end, sizeof(end)); 517 end.ut_tv.tv_sec = time((time_t *)0); 518 end.ut_type = SHUTDOWN_TIME; 519 520 if (Flags & AC_D) { 521 ltm = localtime(&end.ut_tv.tv_sec); 522 if (day >= 0 && day != ltm->tm_yday) { 523 /* 524 * print yesterday's total 525 */ 526 secs = end.ut_tv.tv_sec; 527 secs -= ltm->tm_sec; 528 secs -= 60 * ltm->tm_min; 529 secs -= 3600 * ltm->tm_hour; 530 show_today(Users, head, secs); 531 } 532 } 533 /* 534 * anyone still logged in gets time up to now 535 */ 536 head = log_out(head, &end); 537 538 if (Flags & AC_D) 539 show_today(Users, head, time((time_t *)0)); 540 else { 541 if (Flags & AC_P) 542 show_users(Users); 543 show("total", Total); 544 } 545 return 0; 546} 547 548void 549usage() 550{ 551 (void)fprintf(stderr, 552#ifdef CONSOLE_TTY 553 "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n"); 554#else 555 "ac [-dp] [-t tty] [-w wtmp] [users ...]\n"); 556#endif 557 exit(1); 558} 559