1 /* $NetBSD: pac.c,v 1.15 2000/07/16 21:33:26 jdolecek Exp $ */ 2 3 /* 4 * Copyright (c) 1983, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #include <sys/cdefs.h> 38 #ifndef lint 39 __COPYRIGHT("@(#) Copyright (c) 1983, 1993\n\ 40 The Regents of the University of California. All rights reserved.\n"); 41 #if 0 42 static char sccsid[] = "@(#)pac.c 8.1 (Berkeley) 6/6/93"; 43 #else 44 __RCSID("$NetBSD: pac.c,v 1.15 2000/07/16 21:33:26 jdolecek Exp $"); 45 #endif 46 #endif /* not lint */ 47 48 /* 49 * Do Printer accounting summary. 50 * Currently, usage is 51 * pac [-Pprinter] [-pprice] [-s] [-r] [-c] [-m] [user ...] 52 * to print the usage information for the named people. 53 */ 54 55 #include <sys/param.h> 56 57 #include <dirent.h> 58 #include <stdio.h> 59 #include <stdlib.h> 60 #include <string.h> 61 #include <unistd.h> 62 #include <err.h> 63 64 #include "lp.h" 65 #include "lp.local.h" 66 67 static char *acctfile; /* accounting file (input data) */ 68 static int allflag = 1; /* Get stats on everybody */ 69 static int errs; 70 static int hcount; /* Count of hash entries */ 71 static int mflag = 0; /* disregard machine names */ 72 static int pflag = 0; /* 1 if -p on cmd line */ 73 static float price = 0.02; /* cost per page (or what ever) */ 74 static long price100; /* per-page cost in 100th of a cent */ 75 static int reverse; /* Reverse sort order */ 76 static int sort; /* Sort by cost */ 77 static char *sumfile; /* summary file */ 78 static int summarize; /* Compress accounting file */ 79 80 /* 81 * Grossness follows: 82 * Names to be accumulated are hashed into the following 83 * table. 84 */ 85 86 #define HSHSIZE 97 /* Number of hash buckets */ 87 88 struct hent { 89 struct hent *h_link; /* Forward hash link */ 90 char *h_name; /* Name of this user */ 91 float h_feetpages; /* Feet or pages of paper */ 92 int h_count; /* Number of runs */ 93 }; 94 95 static struct hent *hashtab[HSHSIZE]; /* Hash table proper */ 96 97 static void account __P((FILE *)); 98 static int chkprinter __P((const char *)); 99 static void dumpit __P((void)); 100 static int hash __P((const char *)); 101 static struct hent *enter __P((const char *)); 102 static struct hent *lookup __P((const char *)); 103 static int qucmp __P((const void *, const void *)); 104 static void rewrite __P((void)); 105 static void usage __P((void)); 106 int main __P((int, char * const [])); 107 108 int 109 main(argc, argv) 110 int argc; 111 char * const argv[]; 112 { 113 FILE *acct; 114 int opt; 115 116 while ((opt = getopt(argc, argv, "P:p:scmr")) != -1) { 117 switch(opt) { 118 case 'P': 119 /* 120 * Printer name. 121 */ 122 printer = optarg; 123 continue; 124 125 case 'p': 126 /* 127 * get the price. 128 */ 129 price = atof(optarg); 130 pflag = 1; 131 continue; 132 133 case 's': 134 /* 135 * Summarize and compress accounting file. 136 */ 137 summarize++; 138 continue; 139 140 case 'c': 141 /* 142 * Sort by cost. 143 */ 144 sort++; 145 continue; 146 147 case 'm': 148 /* 149 * disregard machine names for each user 150 */ 151 mflag = 1; 152 continue; 153 154 case 'r': 155 /* 156 * Reverse sorting order. 157 */ 158 reverse++; 159 continue; 160 161 default: 162 usage(); 163 /* NOTREACHED */ 164 } 165 } 166 argc -= optind; 167 argv += optind; 168 169 /* 170 * If there are any arguments left, they're names of users 171 * we want to print info for. In that case, put them in the hash 172 * table and unset allflag. 173 */ 174 for( ; argc > 0; argc--, argv++) { 175 (void)enter(*argv); 176 allflag = 0; 177 } 178 179 if (printer == NULL && (printer = getenv("PRINTER")) == NULL) 180 printer = DEFLP; 181 if (!chkprinter(printer)) { 182 printf("pac: unknown printer %s\n", printer); 183 exit(2); 184 } 185 186 if ((acct = fopen(acctfile, "r")) == NULL) 187 err(1, "%s", acctfile); 188 account(acct); 189 fclose(acct); 190 if ((acct = fopen(sumfile, "r")) != NULL) { 191 account(acct); 192 fclose(acct); 193 } 194 if (summarize) 195 rewrite(); 196 else 197 dumpit(); 198 exit(errs); 199 } 200 201 /* 202 * Read the entire accounting file, accumulating statistics 203 * for the users that we have in the hash table. If allflag 204 * is set, then just gather the facts on everyone. 205 * Note that we must accomodate both the active and summary file 206 * formats here. 207 * Format of accounting file is 208 * feet_per_page [runs_count] [hostname:]username 209 * Some software relies on whitespace between runs_count and hostname:username 210 * being optional (such as Ghostscript's unix-lpr.sh). 211 * 212 * Host names are ignored if the -m flag is present. 213 */ 214 static void 215 account(acct) 216 FILE *acct; 217 { 218 char who[BUFSIZ]; 219 char linebuf[BUFSIZ]; 220 float t; 221 char *cp, *cp2; 222 struct hent *hp; 223 int ic; 224 225 while (fgets(linebuf, BUFSIZ, acct) != NULL) { 226 if (sscanf(linebuf, "%f %d%s", &t, &ic, who) == 0) { 227 sscanf(linebuf, "%f %s", &t, who); 228 ic = 1; 229 } 230 231 /* if -m was specified, don't use the hostname part */ 232 if (mflag && (cp2 = strchr(who, ':'))) 233 cp = cp2 + 1; 234 else 235 cp = who; 236 237 hp = lookup(cp); 238 if (hp == NULL) { 239 if (!allflag) 240 continue; 241 hp = enter(cp); 242 } 243 hp->h_feetpages += t; 244 if (ic) 245 hp->h_count += ic; 246 else 247 hp->h_count++; 248 } 249 } 250 251 /* 252 * Sort the hashed entries by name or footage 253 * and print it all out. 254 */ 255 static void 256 dumpit() 257 { 258 struct hent **base; 259 struct hent *hp, **ap; 260 int hno, c, runs; 261 float feet; 262 263 hp = hashtab[0]; 264 hno = 1; 265 base = (struct hent **) calloc(sizeof hp, hcount); 266 if (base == NULL) 267 err(1, "calloc"); 268 for (ap = base, c = hcount; c--; ap++) { 269 while (hp == NULL) 270 hp = hashtab[hno++]; 271 *ap = hp; 272 hp = hp->h_link; 273 } 274 qsort(base, hcount, sizeof hp, qucmp); 275 printf(" pages/feet runs price %s\n", 276 (mflag ? "login" : "host name and login")); 277 printf(" ---------- ---- -------- ----------------------\n"); 278 feet = 0.0; 279 runs = 0; 280 for (ap = base, c = hcount; c--; ap++) { 281 hp = *ap; 282 runs += hp->h_count; 283 feet += hp->h_feetpages; 284 printf(" %7.2f %4d $%7.2f %s\n", 285 hp->h_feetpages, hp->h_count, 286 hp->h_feetpages * price * hp->h_count, 287 hp->h_name); 288 } 289 if (allflag) { 290 printf(" ---------- ---- -------- ----------------------\n"); 291 printf("Sum:%7.2f %4d $%7.2f\n", feet, runs, 292 feet * price * runs); 293 } 294 } 295 296 /* 297 * Rewrite the summary file with the summary information we have accumulated. 298 */ 299 static void 300 rewrite() 301 { 302 struct hent *hp; 303 int i; 304 FILE *acctf; 305 306 if ((acctf = fopen(sumfile, "w")) == NULL) { 307 warn("%s", sumfile); 308 errs++; 309 return; 310 } 311 for (i = 0; i < HSHSIZE; i++) { 312 hp = hashtab[i]; 313 while (hp != NULL) { 314 fprintf(acctf, "%7.2f\t%s\t%d\n", hp->h_feetpages, 315 hp->h_name, hp->h_count); 316 hp = hp->h_link; 317 } 318 } 319 fflush(acctf); 320 if (ferror(acctf)) { 321 warn("%s", sumfile); 322 errs++; 323 } 324 fclose(acctf); 325 if ((acctf = fopen(acctfile, "w")) == NULL) 326 warn("%s", acctfile); 327 else 328 fclose(acctf); 329 } 330 331 /* 332 * Hashing routines. 333 */ 334 335 /* 336 * Enter the name into the hash table and return the pointer allocated. 337 */ 338 339 static struct hent * 340 enter(name) 341 const char *name; 342 { 343 struct hent *hp; 344 int h; 345 346 if ((hp = lookup(name)) != NULL) 347 return(hp); 348 h = hash(name); 349 hcount++; 350 hp = (struct hent *) calloc(sizeof *hp, 1); 351 if (hp == NULL) 352 err(1, "calloc"); 353 hp->h_name = strdup(name); 354 if (hp->h_name == NULL) 355 err(1, "malloc"); 356 hp->h_feetpages = 0.0; 357 hp->h_count = 0; 358 hp->h_link = hashtab[h]; 359 hashtab[h] = hp; 360 return(hp); 361 } 362 363 /* 364 * Lookup a name in the hash table and return a pointer 365 * to it. 366 */ 367 368 static struct hent * 369 lookup(name) 370 const char *name; 371 { 372 int h; 373 struct hent *hp; 374 375 h = hash(name); 376 for (hp = hashtab[h]; hp != NULL; hp = hp->h_link) 377 if (strcmp(hp->h_name, name) == 0) 378 return(hp); 379 return(NULL); 380 } 381 382 /* 383 * Hash the passed name and return the index in 384 * the hash table to begin the search. 385 */ 386 static int 387 hash(name) 388 const char *name; 389 { 390 int h; 391 const char *cp; 392 393 for (cp = name, h = 0; *cp; h = (h << 2) + *cp++) 394 ; 395 return((h & 0x7fffffff) % HSHSIZE); 396 } 397 398 /* 399 * The qsort comparison routine. 400 * The comparison is ascii collating order 401 * or by feet of typesetter film, according to sort. 402 */ 403 static int 404 qucmp(a, b) 405 const void *a, *b; 406 { 407 struct hent *h1, *h2; 408 int r; 409 410 h1 = *(struct hent **)a; 411 h2 = *(struct hent **)b; 412 if (sort) 413 r = h1->h_feetpages < h2->h_feetpages ? 414 -1 : h1->h_feetpages > h2->h_feetpages; 415 else 416 r = strcmp(h1->h_name, h2->h_name); 417 return(reverse ? -r : r); 418 } 419 420 /* 421 * Perform lookup for printer name or abbreviation -- 422 */ 423 static int 424 chkprinter(s) 425 const char *s; 426 { 427 int stat; 428 429 if ((stat = cgetent(&bp, printcapdb, s)) == -2) { 430 printf("pac: can't open printer description file\n"); 431 exit(3); 432 } else if (stat == -1) 433 return(0); 434 else if (stat == -3) 435 fatal("potential reference loop detected in printcap file"); 436 437 if (cgetstr(bp, "af", &acctfile) == -1) { 438 printf("accounting not enabled for printer %s\n", printer); 439 exit(2); 440 } 441 if (!pflag && (cgetnum(bp, "pc", &price100) == 0)) 442 price = price100/10000.0; 443 sumfile = (char *) calloc(sizeof(char), strlen(acctfile)+5); 444 if (sumfile == NULL) 445 err(1, "pac"); 446 strcpy(sumfile, acctfile); /* XXX: strcpy is safe */ 447 strcat(sumfile, "_sum"); /* XXX: strcat is safe */ 448 return(1); 449 } 450 451 static void 452 usage() 453 { 454 fprintf(stderr, 455 "usage: pac [-Pprinter] [-pprice] [-s] [-c] [-r] [-m] [user ...]\n"); 456 exit(1); 457 } 458