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