1 /* $NetBSD: pac.c,v 1.17 2003/05/17 14:56:40 itojun 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.17 2003/05/17 14:56:40 itojun 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(FILE *); 98 static int chkprinter(const char *); 99 static void dumpit(void); 100 static int hash(const char *); 101 static struct hent *enter(const char *); 102 static struct hent *lookup(const char *); 103 static int qucmp(const void *, const void *); 104 static void rewrite(void); 105 static void usage(void); 106 int main(int, char * const []); 107 108 int 109 main(int argc, char *const argv[]) 110 { 111 FILE *acct; 112 int opt; 113 114 while ((opt = getopt(argc, argv, "P:p:scmr")) != -1) { 115 switch(opt) { 116 case 'P': 117 /* 118 * Printer name. 119 */ 120 printer = optarg; 121 continue; 122 123 case 'p': 124 /* 125 * get the price. 126 */ 127 price = atof(optarg); 128 pflag = 1; 129 continue; 130 131 case 's': 132 /* 133 * Summarize and compress accounting file. 134 */ 135 summarize++; 136 continue; 137 138 case 'c': 139 /* 140 * Sort by cost. 141 */ 142 sort++; 143 continue; 144 145 case 'm': 146 /* 147 * disregard machine names for each user 148 */ 149 mflag = 1; 150 continue; 151 152 case 'r': 153 /* 154 * Reverse sorting order. 155 */ 156 reverse++; 157 continue; 158 159 default: 160 usage(); 161 /* NOTREACHED */ 162 } 163 } 164 argc -= optind; 165 argv += optind; 166 167 /* 168 * If there are any arguments left, they're names of users 169 * we want to print info for. In that case, put them in the hash 170 * table and unset allflag. 171 */ 172 for( ; argc > 0; argc--, argv++) { 173 (void)enter(*argv); 174 allflag = 0; 175 } 176 177 if (printer == NULL && (printer = getenv("PRINTER")) == NULL) 178 printer = DEFLP; 179 if (!chkprinter(printer)) { 180 printf("pac: unknown printer %s\n", printer); 181 exit(2); 182 } 183 184 if ((acct = fopen(acctfile, "r")) == NULL) 185 err(1, "%s", acctfile); 186 account(acct); 187 fclose(acct); 188 if ((acct = fopen(sumfile, "r")) != NULL) { 189 account(acct); 190 fclose(acct); 191 } 192 if (summarize) 193 rewrite(); 194 else 195 dumpit(); 196 exit(errs); 197 } 198 199 /* 200 * Read the entire accounting file, accumulating statistics 201 * for the users that we have in the hash table. If allflag 202 * is set, then just gather the facts on everyone. 203 * Note that we must accomodate both the active and summary file 204 * formats here. 205 * Format of accounting file is 206 * feet_per_page [runs_count] [hostname:]username 207 * Some software relies on whitespace between runs_count and hostname:username 208 * being optional (such as Ghostscript's unix-lpr.sh). 209 * 210 * Host names are ignored if the -m flag is present. 211 */ 212 static void 213 account(FILE *acct) 214 { 215 char who[BUFSIZ]; 216 char linebuf[BUFSIZ]; 217 float t; 218 char *cp, *cp2; 219 struct hent *hp; 220 int ic; 221 222 while (fgets(linebuf, BUFSIZ, acct) != NULL) { 223 /* XXX sizeof(who) == 1024 */ 224 if (sscanf(linebuf, "%f %d%1023s", &t, &ic, who) == 0) { 225 sscanf(linebuf, "%f %1023s", &t, who); 226 ic = 1; 227 } 228 229 /* if -m was specified, don't use the hostname part */ 230 if (mflag && (cp2 = strchr(who, ':'))) 231 cp = cp2 + 1; 232 else 233 cp = who; 234 235 hp = lookup(cp); 236 if (hp == NULL) { 237 if (!allflag) 238 continue; 239 hp = enter(cp); 240 } 241 hp->h_feetpages += t; 242 if (ic) 243 hp->h_count += ic; 244 else 245 hp->h_count++; 246 } 247 } 248 249 /* 250 * Sort the hashed entries by name or footage 251 * and print it all out. 252 */ 253 static void 254 dumpit(void) 255 { 256 struct hent **base; 257 struct hent *hp, **ap; 258 int hno, c, runs; 259 float feet; 260 261 hp = hashtab[0]; 262 hno = 1; 263 base = (struct hent **) calloc(sizeof hp, hcount); 264 if (base == NULL) 265 err(1, "calloc"); 266 for (ap = base, c = hcount; c--; ap++) { 267 while (hp == NULL) 268 hp = hashtab[hno++]; 269 *ap = hp; 270 hp = hp->h_link; 271 } 272 qsort(base, hcount, sizeof hp, qucmp); 273 printf(" pages/feet runs price %s\n", 274 (mflag ? "login" : "host name and login")); 275 printf(" ---------- ---- -------- ----------------------\n"); 276 feet = 0.0; 277 runs = 0; 278 for (ap = base, c = hcount; c--; ap++) { 279 hp = *ap; 280 runs += hp->h_count; 281 feet += hp->h_feetpages; 282 printf(" %7.2f %4d $%7.2f %s\n", 283 hp->h_feetpages, hp->h_count, 284 hp->h_feetpages * price * hp->h_count, 285 hp->h_name); 286 } 287 if (allflag) { 288 printf(" ---------- ---- -------- ----------------------\n"); 289 printf("Sum:%7.2f %4d $%7.2f\n", feet, runs, 290 feet * price * runs); 291 } 292 } 293 294 /* 295 * Rewrite the summary file with the summary information we have accumulated. 296 */ 297 static void 298 rewrite(void) 299 { 300 struct hent *hp; 301 int i; 302 FILE *acctf; 303 304 if ((acctf = fopen(sumfile, "w")) == NULL) { 305 warn("%s", sumfile); 306 errs++; 307 return; 308 } 309 for (i = 0; i < HSHSIZE; i++) { 310 hp = hashtab[i]; 311 while (hp != NULL) { 312 fprintf(acctf, "%7.2f\t%s\t%d\n", hp->h_feetpages, 313 hp->h_name, hp->h_count); 314 hp = hp->h_link; 315 } 316 } 317 fflush(acctf); 318 if (ferror(acctf)) { 319 warn("%s", sumfile); 320 errs++; 321 } 322 fclose(acctf); 323 if ((acctf = fopen(acctfile, "w")) == NULL) 324 warn("%s", acctfile); 325 else 326 fclose(acctf); 327 } 328 329 /* 330 * Hashing routines. 331 */ 332 333 /* 334 * Enter the name into the hash table and return the pointer allocated. 335 */ 336 337 static struct hent * 338 enter(const char *name) 339 { 340 struct hent *hp; 341 int h; 342 343 if ((hp = lookup(name)) != NULL) 344 return(hp); 345 h = hash(name); 346 hcount++; 347 hp = (struct hent *) calloc(sizeof *hp, 1); 348 if (hp == NULL) 349 err(1, "calloc"); 350 hp->h_name = strdup(name); 351 if (hp->h_name == NULL) 352 err(1, "malloc"); 353 hp->h_feetpages = 0.0; 354 hp->h_count = 0; 355 hp->h_link = hashtab[h]; 356 hashtab[h] = hp; 357 return(hp); 358 } 359 360 /* 361 * Lookup a name in the hash table and return a pointer 362 * to it. 363 */ 364 365 static struct hent * 366 lookup(const char *name) 367 { 368 int h; 369 struct hent *hp; 370 371 h = hash(name); 372 for (hp = hashtab[h]; hp != NULL; hp = hp->h_link) 373 if (strcmp(hp->h_name, name) == 0) 374 return(hp); 375 return(NULL); 376 } 377 378 /* 379 * Hash the passed name and return the index in 380 * the hash table to begin the search. 381 */ 382 static int 383 hash(const char *name) 384 { 385 int h; 386 const char *cp; 387 388 for (cp = name, h = 0; *cp; h = (h << 2) + *cp++) 389 ; 390 return((h & 0x7fffffff) % HSHSIZE); 391 } 392 393 /* 394 * The qsort comparison routine. 395 * The comparison is ascii collating order 396 * or by feet of typesetter film, according to sort. 397 */ 398 static int 399 qucmp(const void *a, const void *b) 400 { 401 struct hent *h1, *h2; 402 int r; 403 404 h1 = *(struct hent **)a; 405 h2 = *(struct hent **)b; 406 if (sort) 407 r = h1->h_feetpages < h2->h_feetpages ? 408 -1 : h1->h_feetpages > h2->h_feetpages; 409 else 410 r = strcmp(h1->h_name, h2->h_name); 411 return(reverse ? -r : r); 412 } 413 414 /* 415 * Perform lookup for printer name or abbreviation -- 416 */ 417 static int 418 chkprinter(const char *s) 419 { 420 int stat; 421 422 if ((stat = cgetent(&bp, printcapdb, s)) == -2) { 423 printf("pac: can't open printer description file\n"); 424 exit(3); 425 } else if (stat == -1) 426 return(0); 427 else if (stat == -3) 428 fatal("potential reference loop detected in printcap file"); 429 430 if (cgetstr(bp, "af", &acctfile) == -1) { 431 printf("accounting not enabled for printer %s\n", printer); 432 exit(2); 433 } 434 if (!pflag && (cgetnum(bp, "pc", &price100) == 0)) 435 price = price100/10000.0; 436 sumfile = (char *) calloc(sizeof(char), strlen(acctfile)+5); 437 if (sumfile == NULL) 438 err(1, "pac"); 439 strcpy(sumfile, acctfile); /* XXX: strcpy is safe */ 440 strcat(sumfile, "_sum"); /* XXX: strcat is safe */ 441 return(1); 442 } 443 444 static void 445 usage(void) 446 { 447 fprintf(stderr, 448 "usage: pac [-Pprinter] [-pprice] [-s] [-c] [-r] [-m] [user ...]\n"); 449 exit(1); 450 } 451