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