188004Sgad/*
288004Sgad * ------+---------+---------+---------+---------+---------+---------+---------*
388004Sgad * Copyright (c) 2001  - Garance Alistair Drosehn <gad@FreeBSD.org>.
488004Sgad * All rights reserved.
588004Sgad *
688004Sgad * Redistribution and use in source and binary forms, with or without
788004Sgad * modification, are permitted provided that the following conditions
888004Sgad * are met:
988004Sgad *   1. Redistributions of source code must retain the above copyright
1088004Sgad *      notice, this list of conditions and the following disclaimer.
1188004Sgad *   2. Redistributions in binary form must reproduce the above copyright
1288004Sgad *      notice, this list of conditions and the following disclaimer in the
1388004Sgad *      documentation and/or other materials provided with the distribution.
1488004Sgad *
1588004Sgad * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1688004Sgad * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1788004Sgad * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1888004Sgad * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1988004Sgad * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2088004Sgad * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2188004Sgad * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2288004Sgad * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2388004Sgad * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2488004Sgad * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2588004Sgad * SUCH DAMAGE.
2688004Sgad *
2788004Sgad * The views and conclusions contained in the software and documentation
2888004Sgad * are those of the authors and should not be interpreted as representing
2988004Sgad * official policies, either expressed or implied, of the FreeBSD Project.
3088004Sgad *
3188004Sgad * ------+---------+---------+---------+---------+---------+---------+---------*
3288004Sgad */
3388004Sgad
34117623Sgad#include "lp.cdefs.h"		/* A cross-platform version of <sys/cdefs.h> */
35117623Sgad__FBSDID("$FreeBSD: releng/10.3/usr.sbin/lpr/chkprintcap/skimprintcap.c 117623 2003-07-15 08:48:30Z gad $");
3688004Sgad
3788004Sgad#include <sys/types.h>
3888004Sgad
3988004Sgad#include <ctype.h>
4088004Sgad#include <err.h>
4188004Sgad#include <errno.h>
4288004Sgad#include <grp.h>
4388004Sgad#include <stdio.h>
4488004Sgad#include <string.h>
4588004Sgad#include <stdlib.h>
4688004Sgad#include <unistd.h>
4788004Sgad
4888004Sgad#include <sys/param.h>		/* needed for lp.h but not used here */
4988004Sgad#include <dirent.h>		/* ditto */
5088004Sgad#include "lp.h"
5188004Sgad#include "lp.local.h"
5288004Sgad#include "skimprintcap.h"
5388004Sgad
5488004Sgad/*
5588004Sgad * Save the canonical queue name of the entry that is currently being
5688004Sgad * scanned, in case a warning message is printed for the current queue.
5788004Sgad * Only the first 'QENTRY_MAXLEN' characters will be saved, since this
5888004Sgad * is only for warning messages.   The variable includes space for the
5988004Sgad * string " (entry " and a trailing ")", when the scanner is in the
6088004Sgad * middle of an entry.  When the scanner is not in a specific entry,
6188004Sgad * the variable will be the a null string.
6288004Sgad */
6388004Sgad#define QENTRY_MAXLEN	30
6488004Sgad#define QENTRY_PREFIX	" (entry "
6588004Sgadstatic char	 skim_entryname[sizeof(QENTRY_PREFIX) + QENTRY_MAXLEN + 2];
6688004Sgad
6788004Sgad/*
6888004Sgad * isgraph is defined to work on an 'int', in the range 0 to 255, plus EOF.
6988004Sgad * Define a wrapper which can take 'char', either signed or unsigned.
7088004Sgad */
7188004Sgad#define isgraphch(Anychar)    isgraph(((int) Anychar) & 255)
7288004Sgad
7388004Sgadstruct skiminfo *
7488004Sgadskim_printcap(const char *pcap_fname, int verbosity)
7588004Sgad{
7688004Sgad	struct skiminfo *skinf;
7788004Sgad	char buff[BUFSIZ];
7888004Sgad	char *ch, *curline, *endfield, *lastchar;
7988004Sgad	FILE *pc_file;
8088004Sgad	int missing_nl;
8188004Sgad	enum {NO_CONTINUE, WILL_CONTINUE, BAD_CONTINUE} is_cont, had_cont;
8288004Sgad	enum {CMNT_LINE, ENTRY_LINE, TAB_LINE, TABERR_LINE} is_type, had_type;
8388004Sgad
8488004Sgad	skinf = malloc(sizeof(struct skiminfo));
8588004Sgad	memset(skinf, 0, sizeof(struct skiminfo));
8688004Sgad
8788004Sgad	pc_file = fopen(pcap_fname, "r");
8888004Sgad	if (pc_file == NULL) {
8988004Sgad		warn("fopen(%s)", pcap_fname);
9088004Sgad		skinf->fatalerr++;
9188004Sgad		return (skinf);		/* fatal error */
9288004Sgad	}
9388004Sgad
9488004Sgad	skim_entryname[0] = '0';
9588004Sgad
9688004Sgad	is_cont = NO_CONTINUE;
9788004Sgad	is_type = CMNT_LINE;
9888004Sgad	errno = 0;
9988004Sgad	curline = fgets(buff, sizeof(buff), pc_file);
10088004Sgad	while (curline != NULL) {
10188004Sgad		skinf->lines++;
10288004Sgad
10388004Sgad		/* Check for the expected newline char, and remove it */
10488004Sgad		missing_nl = 0;
10588004Sgad		lastchar = strchr(curline, '\n');
10688004Sgad		if (lastchar != NULL)
10788004Sgad			*lastchar = '\0';
10888004Sgad		else {
10988004Sgad			lastchar = strchr(curline, '\0');
11088004Sgad			missing_nl = 1;
11188004Sgad		}
11288004Sgad		if (curline < lastchar)
11388004Sgad			lastchar--;
11488004Sgad
11588004Sgad		/*
11688004Sgad		 * Check for `\' (continuation-character) at end of line.
11788004Sgad		 * If there is none, then trim off spaces and check again.
11888004Sgad		 * This would be a bad line because it looks like it is
11988004Sgad		 * continued, but it will not be treated that way.
12088004Sgad		 */
12188004Sgad		had_cont = is_cont;
12288004Sgad		is_cont = NO_CONTINUE;
12388004Sgad		if (*lastchar == '\\') {
12488004Sgad			is_cont = WILL_CONTINUE;
12588004Sgad			lastchar--;
12688004Sgad		} else {
12788004Sgad			while ((curline < lastchar) && !isgraphch(*lastchar))
12888004Sgad				lastchar--;
12988004Sgad			if (*lastchar == '\\')
13088004Sgad				is_cont = BAD_CONTINUE;
13188004Sgad		}
13288004Sgad
13388004Sgad		had_type = is_type;
13488004Sgad		is_type = CMNT_LINE;
13588004Sgad		switch (*curline) {
13688004Sgad		case '\0':	/* treat zero-length line as comment */
13788004Sgad		case '#':
13888004Sgad			skinf->comments++;
13988004Sgad			break;
14088004Sgad		case ' ':
14188004Sgad		case '\t':
14288004Sgad			is_type = TAB_LINE;
14388004Sgad			break;
14488004Sgad		default:
14588004Sgad			is_type = ENTRY_LINE;
14688004Sgad			skinf->entries++;
14788004Sgad
14888004Sgad			/* pick up the queue name, to use in warning messages */
14988004Sgad			ch = curline;
15088004Sgad			while ((ch <= lastchar) && (*ch != ':') && (*ch != '|'))
15188004Sgad				ch++;
15288004Sgad			ch--;			/* last char of queue name */
15388004Sgad			strcpy(skim_entryname, QENTRY_PREFIX);
15488004Sgad			if ((ch - curline) > QENTRY_MAXLEN) {
15588004Sgad				strncat(skim_entryname, curline, QENTRY_MAXLEN
15688004Sgad				    - 1);
15788004Sgad				strcat(skim_entryname, "+");
15888004Sgad			} else {
15988004Sgad				strncat(skim_entryname, curline, (ch - curline
16088004Sgad				    + 1));
16188004Sgad			}
16288004Sgad			strlcat(skim_entryname, ")", sizeof(skim_entryname));
16388004Sgad			break;
16488004Sgad		}
16588004Sgad
16688004Sgad		/*
16788004Sgad		 * Check to see if the previous line was a bad contination
16888004Sgad		 * line.  The check is delayed until now so a warning message
16988004Sgad		 * is not printed when a "bad continuation" is on a comment
17088004Sgad		 * line, and it just "continues" into another comment line.
17188004Sgad		*/
17288004Sgad		if (had_cont == BAD_CONTINUE) {
17388004Sgad			if ((had_type != CMNT_LINE) || (is_type != CMNT_LINE) ||
17488004Sgad			    (verbosity > 1)) {
17588004Sgad				skinf->warnings++;
17688004Sgad				warnx("Warning: blanks after trailing '\\',"
17788004Sgad				    " at line %d%s", skinf->lines - 1,
17888004Sgad				    skim_entryname);
17988004Sgad			}
18088004Sgad		}
18188004Sgad
18288004Sgad		/* If we are no longer in an entry, then forget the name */
18388004Sgad		if ((had_cont != WILL_CONTINUE) && (is_type != ENTRY_LINE)) {
18488004Sgad			skim_entryname[0] = '\0';
18588004Sgad		}
18688004Sgad
18788004Sgad		/*
18888004Sgad		 * Print out warning for missing newline, done down here
18988004Sgad		 * so we are sure to have the right entry-name for it.
19088004Sgad		*/
19188004Sgad		if (missing_nl) {
19288004Sgad			skinf->warnings++;
19388004Sgad			warnx("Warning: No newline at end of line %d%s",
19488004Sgad			    skinf->lines, skim_entryname);
19588004Sgad		}
19688004Sgad
19788004Sgad		/*
19888004Sgad		 * Check for start-of-entry lines which do not include a
19988004Sgad		 * ":" character (to indicate the end of the name field).
20088004Sgad		 * This can cause standard printcap processing to ignore
20188004Sgad		 * ALL of the following lines.
20288004Sgad		 * XXXXX - May need to allow for the list-of-names to
20388004Sgad		 *         continue on to the following line...
20488004Sgad		*/
20588004Sgad		if (is_type == ENTRY_LINE) {
20688004Sgad			endfield = strchr(curline, ':');
20788004Sgad			if (endfield == NULL) {
20888004Sgad				skinf->warnings++;
20988004Sgad				warnx("Warning: No ':' to terminate name-field"
21088004Sgad				    " at line %d%s", skinf->lines,
21188004Sgad				    skim_entryname);
21288004Sgad			}
21388004Sgad		}
21488004Sgad
21588004Sgad		/*
21688004Sgad		 * Now check for cases where this line is (or is-not) a
21788004Sgad		 * continuation of the previous line, and a person skimming
21888004Sgad		 * the file would assume it is not (or is) a continuation.
21988004Sgad		*/
22088004Sgad		switch (had_cont) {
22188004Sgad		case NO_CONTINUE:
22288004Sgad		case BAD_CONTINUE:
22388004Sgad			if (is_type == TAB_LINE) {
22488004Sgad				skinf->warnings++;
22588004Sgad				warnx("Warning: values-line after line with"
22688004Sgad				    " NO trailing '\\', at line %d%s",
22788004Sgad				    skinf->lines, skim_entryname);
22888004Sgad			}
22988004Sgad			break;
23088004Sgad
23188004Sgad		case WILL_CONTINUE:
23288004Sgad			if (is_type == ENTRY_LINE) {
23388004Sgad				skinf->warnings++;
23488004Sgad				warnx("Warning: new entry starts after line"
23588004Sgad				    " with trailing '\\', at line %d%s",
23688004Sgad				    skinf->lines, skim_entryname);
23788004Sgad			}
23888004Sgad			break;
23988004Sgad		}
24088004Sgad
24188004Sgad		/* get another line from printcap and repeat loop */
24288004Sgad		curline = fgets(buff, sizeof(buff), pc_file);
24388004Sgad	}
24488004Sgad
24588004Sgad	if (errno != 0) {
24688004Sgad		warn("fgets(%s)", pcap_fname);
24788004Sgad		skinf->fatalerr++;		/* fatal error */
24888004Sgad	}
24988004Sgad
25088004Sgad	if (skinf->warnings > 0)
25188004Sgad		warnx("%4d warnings from skimming %s", skinf->warnings,
25288004Sgad		    pcap_fname);
25388004Sgad
25488004Sgad	if (verbosity)
25588004Sgad		warnx("%4d lines (%d comments), %d entries for %s",
25688004Sgad		    skinf->lines, skinf->comments, skinf->entries, pcap_fname);
25788004Sgad
25888004Sgad	fclose(pc_file);
25988004Sgad	return (skinf);
26088004Sgad}
261